Compare commits

...
49 Commits
Author SHA1 Message Date
moloch-- a8d59daded tidy 2025-12-31 10:12:28 -06:00
moloch-- 9195376993 Refactor survey to huh 2025-12-31 10:04:27 -06:00
moloch-- a6de8e0304 Add profile forms 2025-12-31 09:13:21 -06:00
moloch-- 310619526d Tweak settings 2025-12-31 08:40:02 -06:00
moloch-- 1990777c4b Tweak settings 2025-12-31 08:39:55 -06:00
moloch-- 9570556a87 Refactor to use debug.BuildInfo for version info 2025-12-31 08:27:09 -06:00
moloch-- b563919c19 Update x/crypto 2025-12-31 08:03:33 -06:00
moloch-- f1858ee8f3 Add 'generate beacon' form 2025-12-31 07:58:27 -06:00
moloch-- 46a8313a33 Tweak generate form 2025-12-31 07:51:57 -06:00
moloch-- 868b2c294c Merge branch 'master' into fix/dnsc2 2025-12-30 23:04:32 -06:00
moloch-- 9827b4df4b Add forms pkg 2025-12-30 23:04:03 -06:00
moloch-- a48309a813 Add forms pkg 2025-12-30 23:03:55 -06:00
moloch-- 5df5dec5d4 Add forms pkg 2025-12-30 23:03:46 -06:00
Joe 3ff3ef2ed4 Merge pull request #2076 from BishopFox/fix/dnsc2
Add cleanup to unit tests to avoid excessive disk usage
2025-12-30 21:59:21 -07:00
moloch-- 73cc8c38ff Add cleanup to unit tests to avoid excessive disk usage 2025-12-30 17:47:49 -06:00
Joe ec7e2facbc Merge pull request #2075 from BishopFox/fix/dnsc2
Improvements to DNS C2, Port go-assest.sh to Golang
2025-12-30 15:37:37 -07:00
Joe 4969c2dc51 Merge branch 'master' into fix/dnsc2 2025-12-30 15:36:27 -07:00
moloch-- 94cb60b1f7 Updates to tests and implementation of dns c2 2025-12-30 16:35:37 -06:00
moloch-- 33fcfd54ef Update makefile to make goos/goarch override easier 2025-12-30 13:04:52 -06:00
moloch-- 10bb248e50 Code cleanup 2025-12-30 12:18:05 -06:00
moloch-- e023fd507e Port go-assets.sh to golang with go run 2025-12-30 11:55:56 -06:00
moloch-- 1a9f0f38dd Port go-assets.sh to golang with go run 2025-12-30 10:56:02 -06:00
moloch-- 02831a80bf Cleanup outputs of go-assets.sh 2025-12-30 10:26:24 -06:00
moloch-- 9ccff1798e Expand and update dns c2 unit tests 2025-12-30 09:46:49 -06:00
Joe cd5ef8b5c6 Merge pull request #2063 from BishopFox/3e11dependabot/npm_and_yarn/docs/sliver-docs/next-15.5.9
Bump next from 15.5.7 to 15.5.9 in /docs/sliver-docs
2025-12-30 08:12:38 -07:00
moloch-- dda1fdbbc2 Update dns unit tests 2025-12-30 08:52:26 -06:00
Joe 3b49df5662 Merge pull request #2067 from BishopFox/350cdependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-29 21:44:47 -07:00
Joe 32be71ff8e Merge pull request #2069 from BishopFox/0d1adependabot/github_actions/actions/download-artifact-7
Bump actions/download-artifact from 6 to 7
2025-12-29 21:44:33 -07:00
Joe 3f77da1524 Merge pull request #2068 from BishopFox/f7cedependabot/github_actions/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-29 21:44:19 -07:00
Joe bae3e9e45d Merge pull request #2074 from BishopFox/polite-zig
Update zig download to be more polite and secure issue #2065
2025-12-29 14:52:04 -07:00
moloch-- 205f5b4196 Update zig download to be more polite and secure issue #2065 2025-12-29 12:04:27 -06:00
dependabot[bot] fae7e211a7 Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:52 +00:00
dependabot[bot] 346da14431 Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:47 +00:00
dependabot[bot] 15174f6c31 Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-15 03:02:44 +00:00
dependabot[bot] f29df040bb Bump next from 15.5.7 to 15.5.9 in /docs/sliver-docs
Bumps [next](https://github.com/vercel/next.js) from 15.5.7 to 15.5.9.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.5.7...v15.5.9)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.5.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-12 01:53:24 +00:00
Joe 0bbecc16ec Merge pull request #2060 from BishopFox/go/v1.25.5
Go v1.25.5
2025-12-05 13:29:26 -07:00
moloch-- 0c216161c2 npm update 2025-12-05 12:28:39 -08:00
moloch-- 8ad9c62eb5 Go v1.25.5 2025-12-04 20:12:24 -08:00
Joe 4c99fbfd82 Merge pull request #2059 from BishopFox/e722dependabot/npm_and_yarn/docs/sliver-docs/next-15.5.7
Bump next from 15.5.4 to 15.5.7 in /docs/sliver-docs
2025-12-04 15:48:34 -07:00
dependabot[bot] ed713e199c Bump next from 15.5.4 to 15.5.7 in /docs/sliver-docs
Bumps [next](https://github.com/vercel/next.js) from 15.5.4 to 15.5.7.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.5.4...v15.5.7)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.5.7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-04 20:55:48 +00:00
Joe aacdb71c4a Merge pull request #2058 from BishopFox/e72edependabot/npm_and_yarn/docs/sliver-docs/mdast-util-to-hast-13.2.1
Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /docs/sliver-docs
2025-12-04 13:54:17 -07:00
dependabot[bot] 6968c3aae5 Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /docs/sliver-docs
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-12-02 04:02:29 +00:00
Joe 410fd93025 Merge pull request #2052 from BishopFox/d161dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-23 22:11:44 -07:00
dependabot[bot] f2a0435dd2 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-11-24 03:04:10 +00:00
Joe d66ec956a4 Merge pull request #2037 from BishopFox/2979dependabot/go_modules/tailscale.com-1.90.6
Bump tailscale.com from 1.90.2 to 1.90.6
2025-11-03 12:57:04 -07:00
dependabot[bot] 8229722735 Bump tailscale.com from 1.90.2 to 1.90.6
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.90.2 to 1.90.6.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.90.2...v1.90.6)

---
updated-dependencies:
- dependency-name: tailscale.com
  dependency-version: 1.90.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-11-03 03:04:28 +00:00
Joe dcd81a4db7 Merge pull request #2035 from BishopFox/util/rand
Secure Rand Utils
2025-11-02 10:01:16 -07:00
Joe 2617387417 Merge pull request #2028 from BishopFox/586cdependabot/go_modules/tailscale.com-1.90.2
Bump tailscale.com from 1.88.4 to 1.90.2
2025-11-02 09:39:38 -07:00
dependabot[bot] a5861c43f2 Bump tailscale.com from 1.88.4 to 1.90.2
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.88.4 to 1.90.2.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.88.4...v1.90.2)

---
updated-dependencies:
- dependency-name: tailscale.com
  dependency-version: 1.90.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-10-27 03:04:45 +00:00
1846 changed files with 72586 additions and 276092 deletions

No files matched your search

+5 -5
View File
@@ -22,7 +22,7 @@ jobs:
git build-essential zlib1g zlib1g-dev wget zip unzip
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
@@ -31,7 +31,7 @@ jobs:
run: make servers
- name: Server Artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: servers
path: ./sliver-server_*
@@ -48,7 +48,7 @@ jobs:
go-version: "^1.25"
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
@@ -57,7 +57,7 @@ jobs:
run: make clients
- name: Client Artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: clients
path: ./sliver-client_*
@@ -72,7 +72,7 @@ jobs:
steps:
- run: mkdir -p ./builds
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
path: ./builds
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
go-version: "^1.25"
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# Must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head of the pull request.
@@ -36,7 +36,7 @@ jobs:
git build-essential zlib1g zlib1g-dev wget zip unzip
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Detect package manager
id: detect-package-manager
@@ -66,7 +66,7 @@ jobs:
uses: actions/configure-pages@v5
- name: Restore cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
.next/cache
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
git build-essential zlib1g zlib1g-dev wget zip unzip
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
@@ -63,7 +63,7 @@ jobs:
go-version: "^1.25"
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
@@ -88,7 +88,7 @@ jobs:
go-version: "^1.25"
- name: Check Out Code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
+29 -11
View File
@@ -1,23 +1,41 @@
# Repository Guidelines
## Project Structure & Module Organization
Sliver is a Go monorepo. Core client code lives in `client/` (CLI, assets, transports) and server code in `server/` (C2 services, assets, configs). `implant/` holds implant payload sources, while `util/` provides shared helpers that must stay server-side. Protocol buffers live under `protobuf/`, and documentation assets are in `docs/`. Keep generated artifacts (e.g., `sliver-client`, `sliver-server`, `.downloaded_assets`) out of commits.
- `client/` holds the CLI client and interactive commands.
- `server/` contains the server runtime, C2 logic, configs, and web assets.
- `implant/` is the implant runtime and transport implementations.
- `protobuf/` stores protobuf definitions and generated Go types.
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
- `vendor/` is the vendored Go dependency tree used by builds.
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
## Build, Test, and Development Commands
Use `make` targets to stay consistent:
- `make` (or `make default`) compiles client/server binaries with vendored modules and tag defaults.
- `make client` and `make linux-amd64` build platform-specific emitters; prefer `GOOS/GOARCH` env vars over editing sources.
- `./go-assets.sh` refreshes client asset bundles; rerun when static files change.
Regenerate protobuf bindings with `make pb`. For quick checks, `GOFLAGS="-tags client" go build ./client/...` is acceptable, but ensure the matching `server` build also succeeds.
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
- `make client` builds a CGO-free client; `make debug` keeps symbols.
- `make pb` regenerates protobuf stubs from `protobuf/`.
- `./go-tests.sh` runs the curated Go test suite with build tags.
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
- Go toolchain: the Makefile enforces Go >= 1.24.
## Coding Style & Naming Conventions
Target Go 1.22+ (see `Makefile` validation). Format code with `gofmt` or `goimports` before committing. Keep package imports segregated (stdlib, third-party, internal). Use lower_snake filenames for assets, PascalCase for exported Go symbols, and prefer explicit names (`sessionID`, `operatorConfig`) over abbreviations. Never import `server` packages inside `client`. Avoid introducing CGO unless justified for cross-platform support.
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
- Do not import `server` packages from `client`.
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
## Testing Guidelines
Run targeted unit tests with Go’s tooling and project tags, e.g. `go test -tags=client,go_sqlite ./client/command/...`. Full coverage uses `./build.py`; expect long runtimes and ensure ample CPU/RAM. Add tests beside code (`*_test.go`) and mirror package paths. When adding transports or implants, verify integration logs under `~/.sliver/logs/` for regressions and clean up fixtures.
- Go tests follow standard `_test.go` naming within each package.
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
- Add or update tests when behavior changes; some server packages run longer tests.
## Commit & Pull Request Guidelines
Sign every commit (`git commit -S`) and write imperative, scoped messages ("Fix DNS canary retry" rather than status updates). Keep vendor updates isolated in their own commit. Pull requests should describe the intent, affected modules, testing performed, and reference GitHub issues. Include before/after screenshots only when UI behavior changes (client console). Ensure CI passes and note any required follow-up.
- Commits must be signed for PRs to `master`; work in a feature branch.
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
- Changes to `vendor/` should be in a distinct commit.
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
## Security & Configuration Tips
Default to secure settings: avoid `math/rand` for secrets, prefer vetted crypto (AES-GCM, Curve25519) per `CONTRIBUTING.md`. Lock down file permissions for generated assets and redact secrets from logs. When touching configuration, favor secure defaults even if it means additional setup for operators.
## Security Notes
- Default to secure configuration; fail closed rather than open.
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
+5 -1
View File
@@ -5,6 +5,7 @@
#
# For unit testing:
# docker build --target test .
# docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .
#
# STAGE: base
@@ -31,6 +32,9 @@ RUN cp -vv sliver-server /opt/sliver-server
## Use `--target test` in the docker build command to run this stage
FROM base AS test
ARG GO_TESTS_FLAGS=""
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
RUN apt-get update --fix-missing \
&& apt-get -y upgrade \
&& apt-get -y install \
@@ -39,7 +43,7 @@ RUN apt-get update --fix-missing \
RUN /opt/sliver-server unpack --force
### Run unit tests
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS}
# STAGE: production
## Final dockerized form of Sliver
+11 -34
View File
@@ -16,49 +16,29 @@ endif
# Prerequisites
#
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
EXECUTABLES = uname sed git zip date cut $(GO)
EXECUTABLES = uname sed git date cut $(GO)
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
#
# Version Information
# Build Information
#
GO_VERSION = $(shell $(GO) version)
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 24
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)
VERSION ?= $(shell git describe --abbrev=0)
COMPILED_AT = $(shell date +%s)
RELEASES_URL ?= https://api.github.com/repos/BishopFox/sliver/releases
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
VERSION_PKG = github.com/bishopfox/sliver/client/version
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
GIT_COMMIT = $(shell git rev-parse HEAD)
LDFLAGS = -ldflags "-s -w \
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
# Debug builds shouldn't be stripped (-s -w flags)
LDFLAGS_DEBUG = -ldflags "-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
SED_INPLACE := sed -i
@@ -83,7 +63,9 @@ endif
# If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm)
ifeq ($(MAKECMDGOALS), )
ENV += GOARCH=arm64
ifeq ($(origin GOARCH), undefined)
ENV += GOARCH=arm64
endif
endif
endif
@@ -91,12 +73,6 @@ ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif
@@ -105,9 +81,10 @@ endif
# Targets
#
.PHONY: default
default: clean .downloaded_assets validate-go-version
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
default: clean validate-go-version
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
@@ -199,5 +176,5 @@ clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets:
./go-assets.sh
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env python3
"""
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 os
import sys
import argparse
INFO = "\033[1m\033[36m[*]\033[0m "
WARN = "\033[1m\033[31m[!]\033[0m "
BOLD = "\033[1m"
NORM = "\033[0m"
def exec_cmd(cmd, ignore_status=False):
"""Executes a command with some sugar"""
print("\n" + INFO + " %s\n" % cmd)
status = os.system(cmd)
if status != 0 and not ignore_status:
print("\n" + WARN + "Command did not exit cleanly (%s) " % status)
if input("continue? [Y/n]: ").upper() != "Y":
sys.exit(status)
return status
def docker_rm_volumes():
exec_cmd("docker volume rm $(docker volume ls -q)", ignore_status=True)
def docker_rm_containers():
exec_cmd("docker rm $(docker ps -a -q)", ignore_status=True)
def docker_rm_images():
exec_cmd("docker rmi -f $(docker images -q)", ignore_status=True)
def docker_prune():
exec_cmd("docker image prune -f", ignore_status=True)
def docker_rm_all():
docker_rm_containers()
docker_rm_images()
docker_rm_volumes()
def build():
status = exec_cmd("docker build --target test .")
if status == 0:
print("\n\n" + INFO + "Build successful")
def main(args):
"""Execute the respective task(s) based on cli args"""
if args.rm_images:
docker_rm_images()
if args.rm_containers:
docker_rm_containers()
if args.rm_volumes:
docker_rm_volumes()
if args.rm_all:
docker_rm_all()
if not args.no_prune:
docker_prune()
if not args.no_build:
build()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="automates interactions with docker")
parser.add_argument(
"--no-build",
"-b",
help="do not run build",
dest="no_build",
action="store_true",
)
parser.add_argument(
"--no-prune",
"-np",
help="do not prune images",
dest="no_prune",
action="store_true",
)
parser.add_argument(
"--rm-all",
"-rma",
help="rm all containers, images, and volumes",
dest="rm_all",
action="store_true",
)
parser.add_argument(
"--rm-containers",
"-rmc",
help="rm all containers",
dest="rm_containers",
action="store_true",
)
parser.add_argument(
"--rm-images",
"-rmi",
help="rm all images",
dest="rm_images",
action="store_true",
)
parser.add_argument(
"--rm-volumes",
"-rmv",
help="rm dangling volumes only",
dest="rm_volumes",
action="store_true",
)
try:
main(parser.parse_args())
except KeyboardInterrupt:
print("\n\n" + WARN + "User stopped process")
+10 -24
View File
@@ -23,7 +23,7 @@ import (
"sort"
"github.com/bishopfox/sliver/client/assets"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/bishopfox/sliver/client/forms"
)
func selectConfig() *assets.ClientConfig {
@@ -39,32 +39,18 @@ func selectConfig() *assets.ClientConfig {
}
}
answer := struct{ Config string }{}
qs := getPromptForConfigs(configs)
err := survey.Ask(qs, &answer)
keys := make([]string, 0, len(configs))
for key := range configs {
keys = append(keys, key)
}
sort.Strings(keys)
selection := keys[0]
err := forms.Select("Select a server:", keys, &selection)
if err != nil {
fmt.Println(err.Error())
return nil
}
return configs[answer.Config]
}
func getPromptForConfigs(configs map[string]*assets.ClientConfig) []*survey.Question {
keys := []string{}
for k := range configs {
keys = append(keys, k)
}
sort.Strings(keys)
return []*survey.Question{
{
Name: "config",
Prompt: &survey.Select{
Message: "Select a server:",
Options: keys,
Default: keys[0],
},
},
}
return configs[selection]
}
+3 -5
View File
@@ -24,9 +24,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -62,8 +62,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return
}
@@ -122,8 +121,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
if promptToOverwrite {
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return nil
}
+2 -3
View File
@@ -27,11 +27,11 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/packages"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -313,8 +313,7 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
}
con.PrintWarnf("%s", msgStr)
confirm := false
prompt := &survey.Confirm{Message: "Do you want to continue?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -24,9 +24,9 @@ import (
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -39,8 +39,7 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' alias?", name), &confirm)
if !confirm {
return
}
+5 -12
View File
@@ -27,7 +27,6 @@ import (
"path/filepath"
"slices"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets"
@@ -35,6 +34,7 @@ import (
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/minisign"
)
@@ -95,12 +95,9 @@ func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
if extCount == 1 {
pluralExtensions = ""
}
prompt := &survey.Confirm{
Message: fmt.Sprintf("Install %d alias%s and %d extension%s?",
aliasCount, pluralAliases, extCount, pluralExtensions,
),
}
survey.AskOne(prompt, &confirm)
forms.Confirm(fmt.Sprintf("Install %d alias%s and %d extension%s?",
aliasCount, pluralAliases, extCount, pluralExtensions,
), &confirm)
if !confirm {
return
}
@@ -296,11 +293,7 @@ func getPackageIDFromUser(name string, options map[string]string) string {
// Add a cancel option
optionKeys = append(optionKeys, doNotInstallOption)
options[doNotInstallOption] = doNotInstallPackageName
prompt := &survey.Select{
Message: fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name),
Options: optionKeys,
}
survey.AskOne(prompt, &selectedPackageKey)
forms.Select(fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name), optionKeys, &selectedPackageKey)
selectedPackageID := options[selectedPackageKey]
return selectedPackageID
+2 -3
View File
@@ -8,10 +8,10 @@ import (
"strings"
"sync"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
@@ -602,8 +602,7 @@ func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
if err != nil {
con.PrintErrorf("could not modify armory: %s\n", err)
confirm := false
prompt := &survey.Confirm{Message: "Would you like to revert the properties of the armory back to their previous values?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
if !confirm {
return
} else {
+3 -5
View File
@@ -25,7 +25,6 @@ import (
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -34,6 +33,7 @@ import (
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
)
@@ -89,7 +89,7 @@ func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
selectedUpdates, err = getUpdatesFromUser(con, updateKeys)
if err != nil {
con.PrintErrorf(err.Error() + "\n")
con.PrintErrorf("%s\n", err.Error())
return
}
if len(selectedUpdates) == 0 {
@@ -305,9 +305,7 @@ func getUpdatesFromUser(con *console.SliverClient, updateKeys []UpdateIdentifier
var updateResponse string
con.Println("You can apply all, none, or some updates.\nTo apply some updates, specify the number of a single update, a range (1-3), or a combination of the two (1, 3-5, 7)\n")
err := survey.AskOne(&survey.Input{
Message: "Which updates would you like to apply? [A]ll, [N]one, or some:",
}, &updateResponse)
err := forms.Input("Which updates would you like to apply? [A]ll, [N]one, or some:", &updateResponse)
if err != nil {
// We do not need to set selectionError because an error here means the same thing as responding with "none"
return
+2 -7
View File
@@ -26,9 +26,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -83,12 +82,8 @@ func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a beacon:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a beacon:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+2 -3
View File
@@ -21,10 +21,10 @@ package beacons
import (
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -71,8 +71,7 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
}
con.Println()
confirm := false
prompt := &survey.Confirm{Message: "Prune these beacons?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Prune these beacons?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -29,7 +29,6 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
@@ -38,6 +37,7 @@ import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -547,11 +547,7 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
choices = append(choices, c2profile.Name)
}
prompt := &survey.Select{
Message: "Select a c2 profile",
Options: choices,
}
survey.AskOne(prompt, &c2profile, nil)
_ = forms.Select("Select a c2 profile", choices, &c2profile)
return c2profile
}
+2 -3
View File
@@ -21,10 +21,10 @@ package clean
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -35,8 +35,7 @@ import (
func CleanCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Printf("This command will kill and remove all sessions, beacons and profiles \n")
confirm := false
prompt := &survey.Confirm{Message: "Are you sure you want to destroy everything?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -8,8 +8,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -55,11 +55,7 @@ func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.S
}
selected := ""
prompt := &survey.Select{
Message: "Select a credential:",
Options: options,
}
err = survey.AskOne(prompt, &selected)
err = forms.Select("Select a credential:", options, &selected)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -27,9 +27,9 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -123,7 +123,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -6
View File
@@ -26,9 +26,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/reeflective/readline"
"github.com/spf13/cobra"
@@ -71,12 +71,8 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a debug target:",
Options: options,
}
selected := ""
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a debug target:", options, &selected)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -23,9 +23,9 @@ import (
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
@@ -106,7 +106,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -25,9 +25,9 @@ import (
"path"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -88,7 +88,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n")
confirm := false
err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
err = forms.Confirm("Kill and restart the process?", &confirm)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -2
View File
@@ -22,9 +22,9 @@ import (
"context"
"strconv"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
)
@@ -44,7 +44,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
core.CloseCursedProcessesByBindPort(session.ID, bindPort)
if kill {
confirm := false
err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
err := forms.Confirm("Kill the cursed process?", &confirm)
if err != nil {
con.PrintErrorf("%s", err)
return
+2 -6
View File
@@ -25,10 +25,10 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -95,12 +95,8 @@ func selectCursedProcess(con *console.SliverClient) *core.CursedProcess {
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a curse:",
Options: options,
}
selected := ""
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a curse:", options, &selected)
if err != nil {
con.PrintErrorf("%s\n", err)
return nil
+2 -3
View File
@@ -24,8 +24,8 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
@@ -82,8 +82,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
if len(assemblyArgsStr) > 256 && !inProcess {
con.PrintWarnf(" Injected .NET assembly arguments are limited to 256 characters when using the default fork/exec model.\nConsider using the --in-process flag to execute the .NET assembly in-process and work around this limitation.\n")
confirm := false
prompt := &survey.Confirm{Message: "Do you want to continue?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -23,8 +23,8 @@ import (
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
@@ -145,8 +145,7 @@ func tryCredsFromLoot(con *console.SliverClient) (string, string, []byte) {
privKey []byte
)
confirm := false
prompt := &survey.Confirm{Message: "No credentials provided, use from loot?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("No credentials provided, use from loot?", &confirm)
if confirm {
// cred, err := loot.SelectCredentials(con)
// if err != nil {
+2 -3
View File
@@ -23,9 +23,9 @@ import (
"fmt"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func ExitCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if 0 < len(sessions.Sessions) || 0 < len(beacons.Beacons) {
con.Printf("There are %d active sessions and %d active beacons.\n", len(sessions.Sessions), len(beacons.Beacons))
confirm := false
prompt := &survey.Confirm{Message: "Are you sure you want to exit?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Are you sure you want to exit?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -24,9 +24,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -92,8 +92,7 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl
if promptToOverwrite {
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current install?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -33,12 +33,12 @@ import (
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/packages"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -171,8 +171,7 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current command?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite current command?", &confirm)
if !confirm {
return
}
+3 -5
View File
@@ -25,9 +25,9 @@ import (
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
)
@@ -40,8 +40,7 @@ func ExtensionsRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []s
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension?", name), &confirm)
if !confirm {
return
}
@@ -104,8 +103,7 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
// If path is outside extensions directory, prompt for confirmation
if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) {
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName), &confirm)
if !confirm {
// Skip the forceRemoveAll but continue with the rest
delete(loadedManifests, manifestName)
+2 -3
View File
@@ -30,12 +30,12 @@ import (
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
"gopkg.in/AlecAivazis/survey.v1"
)
func DownloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
@@ -189,8 +189,7 @@ func HandleDownloadResponse(download *sliverpb.Download, cmd *cobra.Command, arg
if _, err := os.Stat(dst); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
_ = forms.Confirm("Overwrite local file?", &overwrite)
if !overwrite {
return
}
+115
View File
@@ -1,11 +1,15 @@
package generate
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -17,6 +21,21 @@ var (
// GenerateBeaconCmd - The main command used to generate implant binaries
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateBeaconForm(cmd, con, args) {
result, err := forms.GenerateBeaconForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Generate beacon form failed: %s\n", err)
return
}
if err := applyGenerateBeaconForm(cmd, result); err != nil {
con.PrintErrorf("Failed to apply generate beacon form values: %s\n", err)
return
}
}
name, config := parseCompileFlags(cmd, con)
if config == nil {
return
@@ -67,3 +86,99 @@ func parseBeaconFlags(cmd *cobra.Command, config *clientpb.ImplantConfig) error
config.BeaconJitter = int64(time.Duration(beaconJitter) * time.Second)
return nil
}
func shouldRunGenerateBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateBeaconForm(cmd *cobra.Command, result *forms.GenerateBeaconFormResult) error {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
return err
}
}
save := strings.TrimSpace(result.Save)
if save != "" {
if err := cmd.Flags().Set("save", save); err != nil {
return err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return err
}
default:
return errors.New("unsupported C2 transport selection")
}
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
return err
}
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
return err
}
return nil
}
func setOptionalInt64Flag(cmd *cobra.Command, name, value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return nil
}
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return err
}
if parsed < 0 {
return fmt.Errorf("%s must be 0 or greater", name)
}
return cmd.Flags().Set(name, strconv.FormatInt(parsed, 10))
}
+74 -21
View File
@@ -32,9 +32,9 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/spin"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
@@ -89,6 +89,21 @@ var (
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
result, err := forms.GenerateForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Generate form failed: %s\n", err)
return
}
if err := applyGenerateForm(cmd, result); err != nil {
con.PrintErrorf("Failed to apply generate form values: %s\n", err)
return
}
}
name, config := parseCompileFlags(cmd, con)
if config == nil {
return
@@ -117,6 +132,58 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
func shouldRunGenerateForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateForm(cmd *cobra.Command, result *forms.GenerateFormResult) error {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
return err
}
}
save := strings.TrimSpace(result.Save)
if save != "" {
if err := cmd.Flags().Set("save", save); err != nil {
return err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
return cmd.Flags().Set("mtls", c2Value)
case "wg":
return cmd.Flags().Set("wg", c2Value)
case "http":
return cmd.Flags().Set("http", c2Value)
case "dns":
return cmd.Flags().Set("dns", c2Value)
case "named-pipe":
return cmd.Flags().Set("named-pipe", c2Value)
case "tcp-pivot":
return cmd.Flags().Set("tcp-pivot", c2Value)
default:
return errors.New("unsupported C2 transport selection")
}
}
func expandPath(path string) string {
// unless path starts with ~
if len(path) == 0 || path[0] != 126 {
@@ -134,13 +201,10 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
save = expandPath(save)
fi, err := os.Stat(save)
if os.IsNotExist(err) {
con.Printf("%s does not exist\n", save)
if strings.HasSuffix(save, "/") {
con.Printf("%s is dir\n", save)
os.MkdirAll(save, 0o700)
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
} else {
con.Printf("%s is not dir\n", save)
saveDir := filepath.Dir(save)
_, err := os.Stat(saveTo)
if os.IsNotExist(err) {
@@ -152,10 +216,8 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
if fi.IsDir() {
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
} else {
con.PrintInfof("%s is not dir\n", save)
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite existing file?", &confirm)
if !confirm {
return "", errors.New("file already exists")
}
@@ -466,9 +528,8 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
console.Bold, target, console.Normal,
)
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
prompt := &survey.Confirm{Message: "Attempt to build generic implant?"}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
if !confirm {
return "", ""
}
@@ -576,7 +637,7 @@ func hasValidC2AdvancedOptions(options url.Values) (bool, error) {
if testValue != "wininet" {
return false, fmt.Errorf("C2 option \"driver\" must be empty for the default driver or \"wininet\" for the wininet driver (Windows only)")
}
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds", "force-base32":
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds":
if testValue != "true" && testValue != "false" {
return false, fmt.Errorf("C2 option \"%s\" must be a boolean value: true or false", key)
}
@@ -738,11 +799,8 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
}
if !strings.HasPrefix(uri.Path, "/pipe/") {
prompt := &survey.Confirm{
Message: fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri),
}
var confirm bool
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri), &confirm)
if !confirm {
return nil, fmt.Errorf("invalid namedpipe path: %s", uri.Path)
}
@@ -1049,8 +1107,7 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
con.PrintWarnf("For more information please read %s\n", crossCompilerInfoURL)
confirm := false
prompt := &survey.Confirm{Message: "Try to compile anyways (will likely fail)?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Try to compile anyways (will likely fail)?", &confirm)
return confirm
}
@@ -1086,11 +1143,7 @@ func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient
choices = append(choices, builder.Name)
}
choice := ""
prompt := &survey.Select{
Message: "Select an external builder:",
Options: choices,
}
survey.AskOne(prompt, &choice, nil)
_ = forms.Select("Select an external builder:", choices, &choice)
for _, builder := range builders {
if builder.Name == choice {
return builder, nil
+2 -3
View File
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -24,8 +24,7 @@ func ImplantsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' build?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' build?", name), &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -21,10 +21,10 @@ package generate
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -42,12 +42,8 @@ func ImplantsStageCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
options = append(options, name)
}
prompt := &survey.MultiSelect{
Message: "Select sessions and beacons to expose:",
Options: options,
}
selected := []string{}
survey.AskOne(prompt, &selected)
_ = forms.MultiSelect("Select sessions and beacons to expose:", options, &selected)
_, err = con.Rpc.StageImplantBuild(context.Background(), &clientpb.ImplantStageReq{Build: selected})
if err != nil {
+183 -2
View File
@@ -19,8 +19,11 @@ package generate
*/
import (
"context"
"errors"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -28,7 +31,22 @@ import (
// ProfilesNewCmd - Create a new implant profile.
func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var name string
if len(args) > 0 {
if shouldRunGenerateProfilesNewForm(cmd, con, args) {
result, err := forms.GenerateProfilesNewForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Profiles new form failed: %s\n", err)
return
}
profileName, err := applyGenerateProfilesNewForm(cmd, result)
if err != nil {
con.PrintErrorf("Failed to apply profiles new form values: %s\n", err)
return
}
name = profileName
} else if len(args) > 0 {
name = args[0]
}
// name := ctx.Args.String("name")
@@ -51,7 +69,22 @@ func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string
// ProfilesNewBeaconCmd - Create a new beacon profile.
func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var name string
if len(args) > 0 {
if shouldRunGenerateProfilesNewBeaconForm(cmd, con, args) {
result, err := forms.GenerateProfilesNewBeaconForm()
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Profiles new beacon form failed: %s\n", err)
return
}
profileName, err := applyGenerateProfilesNewBeaconForm(cmd, result)
if err != nil {
con.PrintErrorf("Failed to apply profiles new beacon form values: %s\n", err)
return
}
name = profileName
} else if len(args) > 0 {
name = args[0]
}
// name := ctx.Args.String("name")
@@ -80,3 +113,151 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
}
}
func shouldRunGenerateProfilesNewForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateProfilesNewForm(cmd *cobra.Command, result *forms.GenerateProfilesNewFormResult) (string, error) {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return "", err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return "", err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
return "", errors.New("profile name required")
}
implantName := strings.TrimSpace(result.Name)
if implantName != "" {
if err := cmd.Flags().Set("name", implantName); err != nil {
return "", err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return "", err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return "", err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return "", err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return "", err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return "", err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return "", err
}
default:
return "", errors.New("unsupported C2 transport selection")
}
return profileName, nil
}
func shouldRunGenerateProfilesNewBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func applyGenerateProfilesNewBeaconForm(cmd *cobra.Command, result *forms.GenerateProfilesNewBeaconFormResult) (string, error) {
if err := cmd.Flags().Set("os", result.OS); err != nil {
return "", err
}
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
return "", err
}
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
return "", errors.New("profile name required")
}
implantName := strings.TrimSpace(result.Name)
if implantName != "" {
if err := cmd.Flags().Set("name", implantName); err != nil {
return "", err
}
}
c2Value := strings.TrimSpace(result.C2Value)
switch result.C2Type {
case "mtls":
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
return "", err
}
case "wg":
if err := cmd.Flags().Set("wg", c2Value); err != nil {
return "", err
}
case "http":
if err := cmd.Flags().Set("http", c2Value); err != nil {
return "", err
}
case "dns":
if err := cmd.Flags().Set("dns", c2Value); err != nil {
return "", err
}
case "named-pipe":
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
return "", err
}
case "tcp-pivot":
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
return "", err
}
default:
return "", errors.New("unsupported C2 transport selection")
}
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
return "", err
}
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
return "", err
}
return profileName, nil
}
+2 -3
View File
@@ -22,8 +22,8 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func ProfilesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' profile?", name)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Remove '%s' profile?", name), &confirm)
if !confirm {
return
}
+3 -10
View File
@@ -26,10 +26,10 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -140,10 +140,7 @@ func TrafficEncodersAddCmd(cmd *cobra.Command, con *console.SliverClient, args [
// saveFailedSample - Save the sample the encoder failed to properly encode/decode.
func saveFailedSample(encoderName string, test *clientpb.TrafficEncoderTest) {
confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName),
}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName), &confirm)
if !confirm {
return
}
@@ -286,10 +283,6 @@ func SelectTrafficEncoder(con *console.SliverClient) string {
}
sort.Strings(encoderNames)
var selectedEncoder string
prompt := &survey.Select{
Message: "Select a traffic encoder:",
Options: encoderNames,
}
survey.AskOne(prompt, &selectedEncoder)
_ = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
return selectedEncoder
}
+2 -6
View File
@@ -25,8 +25,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -86,12 +86,8 @@ func SelectHostIOC(host *clientpb.Host, con *console.SliverClient) (*clientpb.IO
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select an IOC:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select an IOC:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+2 -6
View File
@@ -28,9 +28,9 @@ import (
"text/tabwriter"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
@@ -172,12 +172,8 @@ func SelectHost(con *console.SliverClient) (*clientpb.Host, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a host:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a host:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+3 -3
View File
@@ -22,11 +22,11 @@ import (
"context"
"errors"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -39,7 +39,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
confirm := false
con.PrintWarnf("WARNING: This will kill the remote implant process\n\n")
if session != nil {
survey.AskOne(&survey.Confirm{Message: "Kill the active session?"}, &confirm, nil)
_ = forms.Confirm("Kill the active session?", &confirm)
if !confirm {
return
}
@@ -52,7 +52,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.ActiveTarget.Background()
return
} else if beacon != nil {
survey.AskOne(&survey.Confirm{Message: "Kill the active beacon?"}, &confirm, nil)
_ = forms.Confirm("Kill the active beacon?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -31,9 +31,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -106,11 +106,7 @@ func SelectLoot(cmd *cobra.Command, rpc rpcpb.SliverRPCClient) (*clientpb.Loot,
}
selected := ""
prompt := &survey.Select{
Message: "Select a piece of loot:",
Options: options,
}
err = survey.AskOne(prompt, &selected)
err = forms.Select("Select a piece of loot:", options, &selected)
if err != nil {
return nil, err
}
+2 -3
View File
@@ -26,12 +26,12 @@ import (
"strings"
"unicode/utf8"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -111,8 +111,7 @@ func saveLootToDisk(cmd *cobra.Command, loot *clientpb.Loot) (string, error) {
}
if _, err := os.Stat(saveTo); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
_ = forms.Confirm("Overwrite local file?", &overwrite)
if !overwrite {
return "", nil
}
+2 -3
View File
@@ -21,10 +21,10 @@ package loot
import (
"context"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
@@ -37,8 +37,7 @@ func LootRenameCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
}
oldName := loot.Name
newName := ""
prompt := &survey.Input{Message: "Enter new name: "}
survey.AskOne(prompt, &newName)
_ = forms.Input("Enter new name:", &newName)
loot, err = con.Rpc.LootUpdate(context.Background(), &clientpb.Loot{
ID: loot.ID,
+2 -6
View File
@@ -22,9 +22,9 @@ import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
@@ -121,11 +121,7 @@ func selectWatchtowerConfig(configs *clientpb.MonitoringProviders) (*clientpb.Mo
}
selected := ""
prompt := &survey.Select{
Message: "Select a configuration:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a configuration:", options, &selected)
if err != nil {
return nil, err
}
+2 -6
View File
@@ -27,8 +27,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/rsteube/carapace"
)
@@ -49,11 +49,7 @@ func SelectPivotListener(listeners []*sliverpb.PivotListener, con *console.Slive
}
selected := ""
prompt := &survey.Select{
Message: "Select a beacon task:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a beacon task:", options, &selected)
if err != nil {
return nil, err
}
+2 -3
View File
@@ -21,8 +21,8 @@ package reaction
import (
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -33,8 +33,7 @@ func ReactionReloadCmd(cmd *cobra.Command, con *console.SliverClient, args []str
return
}
confirm := false
prompt := &survey.Confirm{Message: "Reload reactions from disk?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Reload reactions from disk?", &confirm)
if !confirm {
return
}
+2 -3
View File
@@ -22,9 +22,9 @@ import (
"fmt"
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -33,8 +33,7 @@ func ReactionSaveCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
reactionPath := GetReactionFilePath()
if _, err := os.Stat(reactionPath); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Overwrite reactions (%s) on disk?", reactionPath)}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm(fmt.Sprintf("Overwrite reactions (%s) on disk?", reactionPath), &confirm)
if !confirm {
return
}
+3 -10
View File
@@ -22,9 +22,9 @@ import (
"errors"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -77,12 +77,8 @@ func getEventType(cmd *cobra.Command, con *console.SliverClient) (string, error)
}
func selectEventType(con *console.SliverClient) (string, error) {
prompt := &survey.Select{
Message: "Select an event:",
Options: core.ReactableEvents,
}
selection := ""
err := survey.AskOne(prompt, &selection)
err := forms.Select("Select an event:", core.ReactableEvents, &selection)
if err != nil {
return "", err
}
@@ -96,9 +92,6 @@ func selectEventType(con *console.SliverClient) (string, error) {
func userCommands() (string, error) {
text := ""
prompt := &survey.Multiline{
Message: "Enter commands: ",
}
err := survey.AskOne(prompt, &text)
err := forms.Text("Enter commands:", &text)
return text, err
}
+2 -7
View File
@@ -25,9 +25,9 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -65,13 +65,8 @@ func selectReaction(con *console.SliverClient) (*core.Reaction, error) {
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove blank line at the end
// Prompt user with options
prompt := &survey.Select{
Message: "Select a reaction:",
Options: options,
}
selection := ""
err := survey.AskOne(prompt, &selection)
err := forms.Select("Select a reaction:", options, &selection)
if err != nil {
return nil, err
}
+2 -6
View File
@@ -27,8 +27,8 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -85,12 +85,8 @@ func SelectSession(onlyAlive bool, con *console.SliverClient) (*clientpb.Session
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a session:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a session:", options, &selected)
if selected == "" {
return nil, ErrNoSelection
}
+1
View File
@@ -19,6 +19,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
},
GroupID: consts.GenericHelpGroup,
}
settingsCmd.Flags().Bool("show", false, "Show current settings without prompting")
settingsCmd.AddCommand(&cobra.Command{
Use: consts.SaveStr,
Short: "Save the current settings to disk",
+2 -3
View File
@@ -19,9 +19,9 @@ package settings
*/
import (
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
)
@@ -45,8 +45,7 @@ func IsUserAnAdult(con *console.SliverClient) bool {
return true
}
confirm := false
prompt := &survey.Confirm{Message: "This action is bad OPSEC, are you an adult?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
return confirm
}
+67 -8
View File
@@ -19,11 +19,13 @@ package settings
*/
import (
"errors"
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
@@ -39,6 +41,25 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
if shouldRunSettingsForm(cmd, con, args) {
tableStyleOptions := make([]string, 0, len(tableStyles))
for option := range tableStyles {
tableStyleOptions = append(tableStyleOptions, option)
}
result, err := forms.SettingsForm(con.Settings, tableStyleOptions)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Settings form failed: %s\n", err)
return
}
if err := applySettingsForm(con.Settings, result); err != nil {
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
return
}
}
tw := table.NewWriter()
tw.SetStyle(GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
@@ -48,6 +69,7 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
tw.AppendRow(table.Row{"Always Overflow", con.Settings.AlwaysOverflow, "Disable table pagination"})
tw.AppendRow(table.Row{"Vim Mode", con.Settings.VimMode, "Navigation mode, vim style"})
tw.AppendRow(table.Row{"User Connect", con.Settings.UserConnect, "Show operator connect/disconnect events"})
tw.AppendRow(table.Row{"Console Logs", con.Settings.ConsoleLogs, "Log console output to disk"})
con.Printf("%s\n", tw.Render())
}
@@ -91,8 +113,7 @@ func SettingsSmallTerm(cmd *cobra.Command, con *console.SliverClient, args []str
}
}
result := ""
prompt := &survey.Input{Message: "Set small width:"}
err = survey.AskOne(prompt, &result)
err = forms.Input("Set small width:", &result)
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -126,11 +147,7 @@ func SettingsTablesCmd(cmd *cobra.Command, con *console.SliverClient, args []str
options = append(options, option)
}
style := ""
prompt := &survey.Select{
Message: "Choose a style:",
Options: options,
}
err = survey.AskOne(prompt, &style)
err = forms.Select("Choose a style:", options, &style)
if err != nil {
con.PrintErrorf("No selection\n")
return
@@ -173,3 +190,45 @@ func SettingsUserConnect(cmd *cobra.Command, con *console.SliverClient, args []s
con.Settings.UserConnect = !con.Settings.UserConnect
con.PrintInfof("User connect events = %v\n", con.Settings.UserConnect)
}
func shouldRunSettingsForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
}
if len(args) != 0 {
return false
}
if show, err := cmd.Flags().GetBool("show"); err == nil && show {
return false
}
return cmd.Flags().NFlag() == 0
}
func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFormResult) error {
if settings == nil {
return errors.New("settings are required")
}
if result == nil {
return errors.New("settings form result is required")
}
if _, ok := tableStyles[result.TableStyle]; !ok {
return errors.New("invalid table style")
}
width, err := strconv.Atoi(strings.TrimSpace(result.SmallTermWidth))
if err != nil {
return err
}
if width < 1 {
return errors.New("small terminal width must be 1 or greater")
}
settings.TableStyle = result.TableStyle
settings.AutoAdult = result.AutoAdult
settings.BeaconAutoResults = result.BeaconAutoResults
settings.SmallTermWidth = width
settings.AlwaysOverflow = result.AlwaysOverflow
settings.VimMode = result.VimMode
settings.UserConnect = result.UserConnect
settings.ConsoleLogs = result.ConsoleLogs
return nil
}
+2 -5
View File
@@ -22,9 +22,9 @@ import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"golang.org/x/term"
@@ -212,10 +212,7 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
if 1 < len(pages) {
if pageNumber+1 < len(pages) {
nextPage := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("[%d/%d] Continue?", pageNumber+1, len(pages)),
}
survey.AskOne(prompt, &nextPage)
_ = forms.Confirm(fmt.Sprintf("[%d/%d] Continue?", pageNumber+1, len(pages)), &nextPage)
if !nextPage {
break
}
+2 -3
View File
@@ -27,8 +27,8 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
"gopkg.in/AlecAivazis/survey.v1"
)
// SocksStartCmd - Add a new tunneled port forward.
@@ -56,8 +56,7 @@ func SocksStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
con.PrintWarnf("SOCKS proxy authentication credentials are tunneled to the implant\n")
con.PrintWarnf("These credentials are recoverable from the implant's memory!\n\n")
confirm := false
prompt := &survey.Confirm{Message: "Do you understand the implication?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Do you understand the implication?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -29,10 +29,10 @@ import (
"github.com/spf13/cobra"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -214,12 +214,8 @@ func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Se
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.MultiSelect{
Message: "Select sessions and beacons:",
Options: options,
}
selected := []string{}
survey.AskOne(prompt, &selected)
_ = forms.MultiSelect("Select sessions and beacons:", options, &selected)
if len(selected) == 0 {
return nil, nil, fmt.Errorf("no sessions or beacons selected 🤔")
+4 -10
View File
@@ -24,7 +24,6 @@ import (
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
"github.com/bishopfox/sliver/client/command/extensions"
@@ -36,6 +35,7 @@ import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
@@ -750,11 +750,7 @@ func taskResponseDownload(download *sliverpb.Download, con *console.SliverClient
saveTo = "Save to File ..."
)
action := saveTo
prompt := &survey.Select{
Message: "Choose an option:",
Options: []string{dump, saveTo},
}
err := survey.AskOne(prompt, &action, survey.WithValidator(survey.Required))
err := forms.SelectRequired("Choose an option:", []string{dump, saveTo}, &action)
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -769,16 +765,14 @@ func taskResponseDownload(download *sliverpb.Download, con *console.SliverClient
func promptSaveToFile(data []byte, con *console.SliverClient) {
saveTo := ""
saveToPrompt := &survey.Input{Message: "Save to: "}
err := survey.AskOne(saveToPrompt, &saveTo)
err := forms.Input("Save to:", &saveTo)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if _, err := os.Stat(saveTo); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
survey.AskOne(prompt, &confirm)
_ = forms.Confirm("Overwrite existing file?", &confirm)
if !confirm {
return
}
+2 -6
View File
@@ -9,8 +9,8 @@ import (
"text/tabwriter"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/rsteube/carapace"
)
@@ -32,11 +32,7 @@ func SelectBeaconTask(tasks []*clientpb.BeaconTask) (*clientpb.BeaconTask, error
}
selected := ""
prompt := &survey.Select{
Message: "Select a beacon task:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
err := forms.Select("Select a beacon task:", options, &selected)
if err != nil {
return nil, err
}
+6 -12
View File
@@ -28,16 +28,15 @@ import (
"net/url"
"os"
"os/user"
"path"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
@@ -57,14 +56,12 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Println()
con.Println(console.Warn + "You're trying to update over an insecure connection, this is a really bad idea!")
confirm := false
prompt := &survey.Confirm{Message: "Recklessly update?"}
survey.AskOne(prompt, &confirm)
forms.Confirm("Recklessly update?", &confirm)
if !confirm {
return
}
confirm = false
prompt = &survey.Confirm{Message: "Seriously?"}
survey.AskOne(prompt, &confirm)
forms.Confirm("Seriously?", &confirm)
if !confirm {
return
}
@@ -115,9 +112,9 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("No new releases.\n")
}
now := time.Now()
lastCheck := []byte(fmt.Sprintf("%d", now.Unix()))
lastCheck := fmt.Appendf(nil, "%d", now.Unix())
appDir := assets.GetRootAppDir()
lastUpdateCheckPath := path.Join(appDir, consts.LastUpdateCheckFileName)
lastUpdateCheckPath := filepath.Join(appDir, consts.LastUpdateCheckFileName)
err = os.WriteFile(lastUpdateCheckPath, lastCheck, 0o600)
if err != nil {
con.Printf("Failed to save update check time %s", err)
@@ -231,10 +228,7 @@ func updateAvailable(con *console.SliverClient, client *http.Client, release *ve
con.Println()
confirm := false
prompt := &survey.Confirm{
Message: "Download update?",
}
survey.AskOne(prompt, &confirm)
forms.Confirm("Download update?", &confirm)
if confirm {
con.Printf("Please wait ...")
err := downloadAsset(client, serverAsset, saveTo)
+3 -8
View File
@@ -27,12 +27,12 @@ import (
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/beacons"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
@@ -74,8 +74,7 @@ func SessionOrBeaconByID(id string, con *console.SliverClient) (*clientpb.Sessio
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, nil, err
}
if err == nil {
} else {
for _, session := range sessions.Sessions {
if strings.HasPrefix(session.ID, id) {
return session, nil, nil
@@ -162,12 +161,8 @@ func SelectSessionOrBeacon(con *console.SliverClient) (*clientpb.Session, *clien
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a session or beacon:",
Options: options,
}
selected := ""
survey.AskOne(prompt, &selected)
_ = forms.Select("Select a session or beacon:", options, &selected)
if selected == "" {
return nil, nil, ErrNoSelection
}
@@ -27,8 +27,8 @@ import (
"path"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -130,8 +130,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
func confirmAddDirectory() bool {
confirm := false
prompt := &survey.Confirm{Message: "Recursively add entire directory?"}
survey.AskOne(prompt, &confirm, nil)
_ = forms.Confirm("Recursively add entire directory?", &confirm)
return confirm
}
+136
View File
@@ -0,0 +1,136 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// ErrUserAborted exposes the huh user-abort sentinel for callers.
var ErrUserAborted = huh.ErrUserAborted
// GenerateFormResult captures the inputs needed to drive the generate command.
type GenerateFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
}
// GenerateForm prompts for core generate flags and returns the collected values.
func GenerateForm() (*GenerateFormResult, error) {
result := &GenerateFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
huh.NewInput().
Title("Save path (optional)").
Description("File or directory; defaults to the current directory").
Value(&result.Save),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+181
View File
@@ -0,0 +1,181 @@
package forms
import (
"errors"
"strconv"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateBeaconFormResult captures the inputs needed to drive the generate beacon command.
type GenerateBeaconFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
Days string
Hours string
Minutes string
Seconds string
Jitter string
}
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
result := &GenerateBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
huh.NewInput().
Title("Save path (optional)").
Description("File or directory; defaults to the current directory").
Value(&result.Save),
),
huh.NewGroup(
huh.NewInput().
Title("Beacon interval days (optional)").
Description("Leave blank for 0").
Value(&result.Days).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval hours (optional)").
Description("Leave blank for 0").
Value(&result.Hours).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval minutes (optional)").
Description("Leave blank for 0").
Value(&result.Minutes).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval seconds (optional)").
Description("Leave blank for default 60 seconds unless other units are set").
Value(&result.Seconds).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon jitter seconds (optional)").
Description("Leave blank for default 30 seconds").
Value(&result.Jitter).
Validate(validateOptionalNonNegativeInt),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
func validateOptionalNonNegativeInt(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return nil
}
parsed, err := strconv.ParseInt(trimmed, 10, 64)
if err != nil {
return errors.New("must be a whole number")
}
if parsed < 0 {
return errors.New("must be 0 or greater")
}
return nil
}
+139
View File
@@ -0,0 +1,139 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateProfilesNewFormResult captures the inputs needed to drive the profiles new command.
type GenerateProfilesNewFormResult struct {
ProfileName string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
}
// GenerateProfilesNewForm prompts for profiles new flags and returns the collected values.
func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
result := &GenerateProfilesNewFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Profile name").
Value(&result.ProfileName).
Validate(func(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("profile name required")
}
return util.AllowedName(trimmed)
}),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
@@ -0,0 +1,171 @@
package forms
import (
"errors"
"strings"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
// GenerateProfilesNewBeaconFormResult captures the inputs needed to drive the profiles new beacon command.
type GenerateProfilesNewBeaconFormResult struct {
ProfileName string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Days string
Hours string
Minutes string
Seconds string
Jitter string
}
// GenerateProfilesNewBeaconForm prompts for profiles new beacon flags and returns the collected values.
func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, error) {
result := &GenerateProfilesNewBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Profile name").
Value(&result.ProfileName).
Validate(func(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("profile name required")
}
return util.AllowedName(trimmed)
}),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}, &result.OS).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
Height(3).
Value(&result.Format),
),
huh.NewGroup(
huh.NewInput().
Title("Implant name (optional)").
Value(&result.Name).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
return util.AllowedName(strings.ToLower(value))
}),
huh.NewSelect[string]().
Title("C2 transport").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("mTLS", "mtls"),
huh.NewOption("WireGuard", "wg"),
huh.NewOption("HTTP(S)", "http"),
huh.NewOption("DNS", "dns"),
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Named pipe", "named-pipe"),
huh.NewOption("TCP pivot", "tcp-pivot"),
)
}
return options
}, &result.OS).
Height(6).
Value(&result.C2Type),
huh.NewInput().
Title("C2 connection string(s)").
Description("Comma separated; example: 1.2.3.4:8888 or https://c2.example.com").
Value(&result.C2Value).
Validate(func(value string) error {
if strings.TrimSpace(value) == "" {
return errors.New("connection string required")
}
return nil
}),
),
huh.NewGroup(
huh.NewInput().
Title("Beacon interval days (optional)").
Description("Leave blank for 0").
Value(&result.Days).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval hours (optional)").
Description("Leave blank for 0").
Value(&result.Hours).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval minutes (optional)").
Description("Leave blank for 0").
Value(&result.Minutes).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon interval seconds (optional)").
Description("Leave blank for default 60 seconds unless other units are set").
Value(&result.Seconds).
Validate(validateOptionalNonNegativeInt),
huh.NewInput().
Title("Beacon jitter seconds (optional)").
Description("Leave blank for default 30 seconds").
Value(&result.Jitter).
Validate(validateOptionalNonNegativeInt),
),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
+154
View File
@@ -0,0 +1,154 @@
package forms
import (
"errors"
"strings"
"github.com/charmbracelet/huh"
)
const defaultSelectHeight = 10
// Confirm prompts for a yes/no answer.
func Confirm(title string, value *bool) error {
if value == nil {
return errors.New("confirm value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(title).
Value(value),
),
)
return form.Run()
}
// Input prompts for a single-line string.
func Input(title string, value *string) error {
if value == nil {
return errors.New("input value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title(title).
Value(value),
),
)
return form.Run()
}
// Text prompts for multi-line input.
func Text(title string, value *string) error {
if value == nil {
return errors.New("text value is required")
}
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title(title).
ExternalEditor(false).
Value(value),
),
)
return form.Run()
}
// Select prompts for a single selection from options.
func Select(title string, options []string, value *string) error {
return selectPrompt(title, options, value, false)
}
// SelectRequired prompts for a single selection and requires a non-empty value.
func SelectRequired(title string, options []string, value *string) error {
return selectPrompt(title, options, value, true)
}
// MultiSelect prompts for selecting multiple values.
func MultiSelect(title string, options []string, value *[]string) error {
if value == nil {
return errors.New("multi-select value is required")
}
if len(options) == 0 {
return errors.New("multi-select options are required")
}
field := huh.NewMultiSelect[string]().
Title(title).
Options(makeStringOptions(options)...).
Height(listHeight(len(options))).
Value(value)
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
}
func selectPrompt(title string, options []string, value *string, required bool) error {
if value == nil {
return errors.New("select value is required")
}
if len(options) == 0 {
return errors.New("select options are required")
}
ensureSelectedValue(options, value)
field := huh.NewSelect[string]().
Title(title).
Options(makeStringOptions(options)...).
Height(listHeight(len(options))).
Value(value)
if required {
field.Validate(func(val string) error {
if strings.TrimSpace(val) == "" {
return errors.New("selection required")
}
return nil
})
}
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
}
func ensureSelectedValue(options []string, value *string) {
if value == nil || len(options) == 0 {
return
}
if *value == "" {
*value = options[0]
return
}
for _, option := range options {
if option == *value {
return
}
}
*value = options[0]
}
func makeStringOptions(options []string) []huh.Option[string] {
converted := make([]huh.Option[string], 0, len(options))
for _, option := range options {
converted = append(converted, huh.NewOption(option, option))
}
return converted
}
func listHeight(count int) int {
if count < 1 {
return 1
}
if count > defaultSelectHeight {
return defaultSelectHeight
}
return count
}
+113
View File
@@ -0,0 +1,113 @@
package forms
import (
"errors"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/charmbracelet/huh"
)
// SettingsFormResult captures the inputs needed to update client settings.
type SettingsFormResult struct {
TableStyle string
AutoAdult bool
BeaconAutoResults bool
SmallTermWidth string
AlwaysOverflow bool
VimMode bool
UserConnect bool
ConsoleLogs bool
}
// SettingsForm prompts for client settings and returns the collected values.
func SettingsForm(settings *assets.ClientSettings, tableStyleOptions []string) (*SettingsFormResult, error) {
if settings == nil {
return nil, errors.New("settings are required")
}
if len(tableStyleOptions) == 0 {
return nil, errors.New("no table styles available")
}
result := &SettingsFormResult{
TableStyle: settings.TableStyle,
AutoAdult: settings.AutoAdult,
BeaconAutoResults: settings.BeaconAutoResults,
SmallTermWidth: strconv.Itoa(settings.SmallTermWidth),
AlwaysOverflow: settings.AlwaysOverflow,
VimMode: settings.VimMode,
UserConnect: settings.UserConnect,
ConsoleLogs: settings.ConsoleLogs,
}
if result.TableStyle == "" {
result.TableStyle = tableStyleOptions[0]
}
styleOptions := make([]huh.Option[string], 0, len(tableStyleOptions))
for _, option := range tableStyleOptions {
styleOptions = append(styleOptions, huh.NewOption(option, option))
}
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Table style").
Options(styleOptions...).
Height(len(styleOptions)).
Value(&result.TableStyle),
huh.NewInput().
Title("Small terminal width").
Description("Omit some table columns when the terminal is narrower than this value").
Value(&result.SmallTermWidth).
Validate(validateRequiredPositiveInt),
huh.NewConfirm().
Title("Always overflow tables").
Description("Disable table pagination").
Value(&result.AlwaysOverflow),
).Title("Display"),
huh.NewGroup(
huh.NewConfirm().
Title("Auto accept OPSEC warnings").
Description("Skip confirmation prompts for risky actions").
Value(&result.AutoAdult),
huh.NewConfirm().
Title("Auto display beacon results").
Description("Show beacon task results when tasks complete").
Value(&result.BeaconAutoResults),
huh.NewConfirm().
Title("Vim navigation mode").
Description("Use vim-style navigation in the console").
Value(&result.VimMode),
huh.NewConfirm().
Title("User connect events").
Description("Show operator connect and disconnect events").
Value(&result.UserConnect),
huh.NewConfirm().
Title("Console logs").
Description("Log console output to disk").
Value(&result.ConsoleLogs),
).Title("Behavior"),
)
if err := form.Run(); err != nil {
return nil, err
}
return result, nil
}
func validateRequiredPositiveInt(value string) error {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return errors.New("value required")
}
parsed, err := strconv.Atoi(trimmed)
if err != nil {
return errors.New("must be a whole number")
}
if parsed < 1 {
return errors.New("must be 1 or greater")
}
return nil
}
+105 -8
View File
@@ -19,15 +19,23 @@ package version
*/
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
const (
normal = "\033[0m"
bold = "\033[1m"
defaultVersion = "devel"
defaultReleasesURL = "https://api.github.com/repos/BishopFox/sliver/releases"
)
var (
@@ -47,22 +55,111 @@ var (
CompiledAt string
)
func init() {
if GithubReleasesURL == "" {
GithubReleasesURL = defaultReleasesURL
}
applyBuildInfo()
}
func applyBuildInfo() {
info, ok := debug.ReadBuildInfo()
if !ok || info == nil {
ensureDefaults()
return
}
if GoVersion == "" {
if info.GoVersion != "" {
GoVersion = info.GoVersion
} else {
GoVersion = runtime.Version()
}
}
if Version == "" {
Version = normalizeVersion(info.Main.Version)
}
settings := buildSettings(info.Settings)
if GitCommit == "" {
GitCommit = settings["vcs.revision"]
}
if GitDirty == "" && settings["vcs.modified"] == "true" {
GitDirty = "Dirty"
}
if CompiledAt == "" {
if vcsTime := settings["vcs.time"]; vcsTime != "" {
if compiled, err := time.Parse(time.RFC3339, vcsTime); err == nil {
CompiledAt = strconv.FormatInt(compiled.Unix(), 10)
}
}
}
ensureDefaults()
}
func ensureDefaults() {
if Version == "" {
Version = defaultVersion
}
if GoVersion == "" {
GoVersion = runtime.Version()
}
}
func buildSettings(settings []debug.BuildSetting) map[string]string {
values := make(map[string]string, len(settings))
for _, setting := range settings {
values[setting.Key] = setting.Value
}
return values
}
func normalizeVersion(version string) string {
if version == "" || version == "(devel)" {
return defaultVersion
}
if canonical := canonicalSemver(version); canonical != "" {
return canonical
}
return version
}
func canonicalSemver(version string) string {
version = strings.TrimSpace(version)
if version == "" || version == "(devel)" {
return ""
}
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
return semver.Canonical(version)
}
// SemanticVersion - Get the structured sematic version
func SemanticVersion() []int {
semVer := []int{}
version := Version
if strings.HasPrefix(version, "v") {
version = version[1:]
semVer := make([]int, 3)
canonical := canonicalSemver(Version)
if canonical == "" {
return semVer
}
for _, part := range strings.Split(version, ".") {
number, _ := strconv.Atoi(part)
semVer = append(semVer, number)
base := strings.TrimPrefix(canonical, "v")
if idx := strings.Index(base, "-"); idx != -1 {
base = base[:idx]
}
parts := strings.Split(base, ".")
for i := 0; i < 3 && i < len(parts); i++ {
semVer[i], _ = strconv.Atoi(parts[i])
}
return semVer
}
// Compiled - Get time this binary was compiled
func Compiled() (time.Time, error) {
if CompiledAt == "" {
return time.Unix(0, 0), errors.New("compiled time not available")
}
compiled, err := strconv.ParseInt(CompiledAt, 10, 64)
if err != nil {
return time.Unix(0, 0), err
@@ -74,7 +171,7 @@ func Compiled() (time.Time, error) {
func FullVersion() string {
ver := fmt.Sprintf("%s", Version)
compiled, err := Compiled()
if err != nil {
if err == nil {
ver += fmt.Sprintf(" - Compiled %s", compiled.String())
}
if GitCommit != "" {
+12 -33
View File
@@ -6,9 +6,9 @@ import (
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
const (
@@ -90,42 +90,21 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
return nil, err
}
mySemVer := SemanticVersion()
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range *releases {
if release.Prerelease && !prereleases {
continue
}
releaseSemVer := parseGitTag(release.TagName)
for index, myVer := range mySemVer {
if index < len(releaseSemVer) {
if releaseSemVer[index] == myVer {
continue
}
if releaseSemVer[index] < myVer {
break
}
if myVer < releaseSemVer[index] {
return release, nil
}
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
}
// parseGitTag - Get the structured sematic version
func parseGitTag(tag string) []int {
semVer := []int{}
version := tag
if strings.Contains(version, "-") {
version = strings.Split(version, "-")[0]
}
if strings.HasPrefix(version, "v") {
version = version[1:]
}
for _, part := range strings.Split(version, ".") {
number, _ := strconv.Atoi(part)
semVer = append(semVer, number)
}
return semVer
}
+692 -637
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -26,7 +26,7 @@
"framer-motion": "^12.23.22",
"fuse.js": "^7.1.0",
"lunr": "^2.3.9",
"next": "^15.5.4",
"next": "^15.5.9",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -29,6 +29,5 @@ generate --http http://example.com?driver=wininet
- `retry-count` - Upon query failure, retry this many times before failing.
- `workers-per-resolver` - Number of worker goroutines per functional DNS resolver.
- `max-errors` - Max number of query errors before failing (integer parsed by `strconv.Atoi`).
- `force-base32` - Always use Base 32 encoding.
- `force-resolv-conf` - Force the use of a provided `resolv.conf`. Note that you'll need to URL encode the newlines/etc of the file contents into the parameter.
- `resolvers` - Force the use of specific DNS resolvers. You can supply more than one by separating them with `+`, i.e. `...?resolvers=1.1.1.1+9.9.9.9`
-312
View File
@@ -1,312 +0,0 @@
#!/bin/bash
# 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/>.
set -e
# Creates the static go asset archives
GO_VER="1.25.3"
GARBLE_VER="1.25.3"
ZIG_VER="0.15.1"
# Zig significantly throttles downloads from the main site, so we use
# community mirrors. We fetch the list of mirrors at runtime, but
# fall back to a set of default mirrors if the fetch fails.
# We also use the official public key for verifying Zig downloads from mirrors.
ZIG_MINISIGN_PUBKEY="RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U"
DEFAULT_ZIG_MIRRORS=(
"https://pkg.machengine.org/zig"
"https://zigmirror.hryx.net/zig"
"https://zig.linus.dev/zig"
"https://zig.squirl.dev"
"https://zig.florent.dev"
"https://zig.mirror.mschae23.de/zig"
"https://zigmirror.meox.dev"
)
ZIG_MIRRORS=()
MINISIGN_HELPER=""
MINISIGN_HELPER_DIR=""
BLOAT_FILES="AUTHORS CONTRIBUTORS PATENTS VERSION favicon.ico robots.txt SECURITY.md CONTRIBUTING.md README.md ./doc ./test ./api ./misc"
if ! [ -x "$(command -v curl)" ]; then
echo 'Error: curl is not installed.' >&2
exit 1
fi
if ! [ -x "$(command -v zip)" ]; then
echo 'Error: zip is not installed.' >&2
exit 1
fi
if ! [ -x "$(command -v unzip)" ]; then
echo 'Error: unzip is not installed.' >&2
exit 1
fi
if ! [ -x "$(command -v tar)" ]; then
echo 'Error: tar is not installed.' >&2
exit 1
fi
if ! [ -x "$(command -v go)" ]; then
echo 'Error: go is not installed.' >&2
exit 1
fi
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
OUTPUT_DIR=$REPO_DIR/server/assets/fs
mkdir -p $OUTPUT_DIR
WORK_DIR=`mktemp -d`
echo "-----------------------------------------------------------------"
echo "$WORK_DIR (Output: $OUTPUT_DIR)"
echo "-----------------------------------------------------------------"
cd $WORK_DIR
# Load official Zig mirror list with a fallback to bundled defaults.
load_zig_mirrors() {
local mirrors_text
local -a mirrors=()
if mirrors_text=$(curl -sS -L --fail https://ziglang.org/download/community-mirrors.txt 2>/dev/null); then
while IFS= read -r line; do
line="${line%%#*}"
line="$(printf '%s' "$line" | sed -e 's/[[:space:]]*$//' -e 's/^[[:space:]]*//')"
if [[ -n "$line" ]]; then
mirrors+=("$line")
fi
done <<< "$mirrors_text"
fi
if [ "${#mirrors[@]}" -eq 0 ]; then
mirrors=("${DEFAULT_ZIG_MIRRORS[@]}")
fi
ZIG_MIRRORS=("${mirrors[@]}")
}
# Shuffle mirror list for each download attempt to spread load.
randomized_mirrors() {
if [ "${#ZIG_MIRRORS[@]}" -eq 0 ]; then
load_zig_mirrors
fi
local -a shuffled=("${ZIG_MIRRORS[@]}")
local i j temp
for ((i=${#shuffled[@]}-1; i>0; i--)); do
j=$((RANDOM % (i + 1)))
temp=${shuffled[i]}
shuffled[i]=${shuffled[j]}
shuffled[j]=$temp
done
printf '%s\n' "${shuffled[@]}"
}
# Build a temporary helper that verifies signatures via util/minisign.
ensure_minisign_helper() {
if [ -n "$MINISIGN_HELPER" ] && [ -x "$MINISIGN_HELPER" ]; then
return
fi
local helper_dir
helper_dir=$(mktemp -d "$REPO_DIR/.zig-verify.XXXXXX")
if ! (cd "$REPO_DIR" && GO111MODULE=on GOFLAGS=-mod=vendor go build -o "$helper_dir/zig-verify" ./util/cmd/zigverify); then
echo 'Error: unable to build minisign verification helper.' >&2
exit 1
fi
MINISIGN_HELPER="$helper_dir/zig-verify"
MINISIGN_HELPER_DIR="$helper_dir"
trap 'rm -rf "$MINISIGN_HELPER_DIR"' EXIT
}
verify_minisig() {
ensure_minisign_helper
ZIG_PUBLIC_KEY="$ZIG_MINISIGN_PUBKEY" "$MINISIGN_HELPER" "$1" "$2"
}
download_zig() {
local platform="$1"
local arch="$2"
local remote_name="$3"
local local_name="$4"
local dest_dir="$OUTPUT_DIR/$platform/$arch"
local dest_path="$dest_dir/$local_name"
local minisig_url="https://ziglang.org/download/$ZIG_VER/$remote_name.minisig"
local mirror_url
local success=0
mkdir -p "$dest_dir"
rm -f "$dest_path"
while read -r mirror; do
mirror_url="${mirror%/}/$ZIG_VER/$remote_name"
echo "Attempting Zig download from $mirror_url"
local tmp_tar
local tmp_sig
tmp_tar=$(mktemp)
tmp_sig=$(mktemp)
local verification_failed=0
if curl -L --fail --output "$tmp_tar" "$mirror_url"; then
if curl -L --fail --output "$tmp_sig" "$minisig_url"; then
if verify_minisig "$tmp_tar" "$tmp_sig"; then
mv "$tmp_tar" "$dest_path"
rm -f "$tmp_sig"
echo "Downloaded and verified Zig package -> $dest_path"
success=1
break
else
echo "[!] Signature verification failed for $mirror_url" >&2
verification_failed=1
fi
fi
fi
if [ "$verification_failed" -eq 1 ]; then
echo "[!] Deleting corrupted download $tmp_tar" >&2
fi
rm -f "$tmp_tar" "$tmp_sig"
echo "[!] Failed to download or verify Zig from $mirror_url" >&2
done < <(randomized_mirrors)
if [ "$success" -ne 1 ]; then
echo "[!] Error: unable to download and verify Zig package $remote_name" >&2
exit 1
fi
}
# --- Darwin (amd64) ---
curl --output go$GO_VER.darwin-amd64.tar.gz https://dl.google.com/go/go$GO_VER.darwin-amd64.tar.gz
tar xvf go$GO_VER.darwin-amd64.tar.gz
cd go
rm -rf $BLOAT_FILES
zip -r ../src.zip ./src # Zip up /src we only need to do this once
rm -rf ./src
rm -f ./pkg/tool/darwin_amd64/doc
rm -f ./pkg/tool/darwin_amd64/tour
rm -f ./pkg/tool/darwin_amd64/test2json
cd ..
cp -vv src.zip $OUTPUT_DIR/src.zip
rm -f src.zip
zip -r darwin-go.zip ./go
mkdir -p $OUTPUT_DIR/darwin/amd64
cp -vv darwin-go.zip $OUTPUT_DIR/darwin/amd64/go.zip
rm -rf ./go
rm -f darwin-go.zip go$GO_VER.darwin-amd64.tar.gz
# --- Darwin (arm64) ---
curl --output go$GO_VER.darwin-arm64.tar.gz https://dl.google.com/go/go$GO_VER.darwin-arm64.tar.gz
tar xvf go$GO_VER.darwin-arm64.tar.gz
cd go
rm -rf $BLOAT_FILES
zip -r ../src.zip ./src # Zip up /src we only need to do this once
rm -rf ./src
rm -f ./pkg/tool/darwin_arm64/doc
rm -f ./pkg/tool/darwin_arm64/tour
rm -f ./pkg/tool/darwin_arm64/test2json
cd ..
cp -vv src.zip $OUTPUT_DIR/src.zip
rm -f src.zip
zip -r darwin-go.zip ./go
mkdir -p $OUTPUT_DIR/darwin/arm64
cp -vv darwin-go.zip $OUTPUT_DIR/darwin/arm64/go.zip
rm -rf ./go
rm -f darwin-go.zip go$GO_VER.darwin-arm64.tar.gz
# --- Linux (amd64) ---
curl --output go$GO_VER.linux-amd64.tar.gz https://dl.google.com/go/go$GO_VER.linux-amd64.tar.gz
tar xvf go$GO_VER.linux-amd64.tar.gz
cd go
rm -rf $BLOAT_FILES
rm -rf ./src
rm -f ./pkg/tool/linux_amd64/doc
rm -f ./pkg/tool/linux_amd64/tour
rm -f ./pkg/tool/linux_amd64/test2json
cd ..
zip -r linux-go.zip ./go
mkdir -p $OUTPUT_DIR/linux/amd64
cp -vv linux-go.zip $OUTPUT_DIR/linux/amd64/go.zip
rm -rf ./go
rm -f linux-go.zip go$GO_VER.linux-amd64.tar.gz
# --- Linux (arm64) ---
curl --output go$GO_VER.linux-arm64.tar.gz https://dl.google.com/go/go$GO_VER.linux-arm64.tar.gz
tar xvf go$GO_VER.linux-arm64.tar.gz
cd go
rm -rf $BLOAT_FILES
rm -rf ./src
rm -f ./pkg/tool/linux_arm64/doc
rm -f ./pkg/tool/linux_arm64/tour
rm -f ./pkg/tool/linux_arm64/test2json
cd ..
zip -r linux-go.zip ./go
mkdir -p $OUTPUT_DIR/linux/arm64
cp -vv linux-go.zip $OUTPUT_DIR/linux/arm64/go.zip
rm -rf ./go
rm -f linux-go.zip go$GO_VER.linux-arm64.tar.gz
# --- Windows ---
curl --output go$GO_VER.windows-amd64.zip https://dl.google.com/go/go$GO_VER.windows-amd64.zip
unzip go$GO_VER.windows-amd64.zip
cd go
rm -rf $BLOAT_FILES
rm -rf ./src
rm -f ./pkg/tool/windows_amd64/doc.exe
rm -f ./pkg/tool/windows_amd64/tour.exe
rm -f ./pkg/tool/windows_amd64/test2json.exe
cd ..
zip -r windows-go.zip ./go
mkdir -p $OUTPUT_DIR/windows/amd64
cp -vv windows-go.zip $OUTPUT_DIR/windows/amd64/go.zip
rm -rf ./go
rm -f windows-go.zip go$GO_VER.windows-amd64.zip
echo "-----------------------------------------------------------------"
echo " Zig"
echo "-----------------------------------------------------------------"
download_zig "darwin" "amd64" "zig-x86_64-macos-$ZIG_VER.tar.xz" "zig.tar.xz"
download_zig "darwin" "arm64" "zig-aarch64-macos-$ZIG_VER.tar.xz" "zig.tar.xz"
download_zig "linux" "amd64" "zig-x86_64-linux-$ZIG_VER.tar.xz" "zig.tar.xz"
download_zig "linux" "arm64" "zig-aarch64-linux-$ZIG_VER.tar.xz" "zig.tar.xz"
# Windows ships a zip instead of a tarball
download_zig "windows" "amd64" "zig-x86_64-windows-$ZIG_VER.zip" "zig.zip"
echo "-----------------------------------------------------------------"
echo " Garble"
echo "-----------------------------------------------------------------"
echo "curl -L --fail --output $OUTPUT_DIR/linux/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-amd64"
curl -L --fail --output $OUTPUT_DIR/linux/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-amd64
echo "curl -L --fail --output $OUTPUT_DIR/linux/arm64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-arm64"
curl -L --fail --output $OUTPUT_DIR/linux/arm64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-arm64
echo "curl -L --fail --output $OUTPUT_DIR/windows/amd64/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows-amd64.exe"
curl -L --fail --output $OUTPUT_DIR/windows/amd64/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows-amd64.exe
echo "curl -L --fail --output $OUTPUT_DIR/darwin/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_darwin-amd64"
curl -L --fail --output $OUTPUT_DIR/darwin/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_darwin-amd64
echo "curl -L --fail --output $OUTPUT_DIR/darwin/arm64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_darwin-arm64"
curl -L --fail --output $OUTPUT_DIR/darwin/arm64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_darwin-arm64
# --- Cleanup ---
echo -e "clean up: $WORK_DIR"
rm -rf $WORK_DIR
echo -e "\n[*] All done\n"
+16 -5
View File
@@ -17,6 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
SKIP_GENERATE=0
for arg in "$@"; do
if [ "$arg" = "--skip-generate" ]; then
SKIP_GENERATE=1
fi
done
echo "----------------------------------------------------------------"
echo "WARNING: Running unit tests on slow systems can take a LONG time"
echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
@@ -180,10 +187,14 @@ else
fi
# server / generate
export GOPROXY=off
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
:
if [ "$SKIP_GENERATE" -eq 0 ]; then
export GOPROXY=off
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
:
else
cat ~/.sliver/logs/sliver.log
exit 1
fi
else
cat ~/.sliver/logs/sliver.log
exit 1
echo "Skipping ./server/generate tests (--skip-generate)"
fi
+32 -31
View File
@@ -1,18 +1,20 @@
module github.com/bishopfox/sliver
go 1.25.1
go 1.25.3
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
require (
filippo.io/age v1.2.1
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/alecthomas/chroma v0.10.0
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3
github.com/charmbracelet/lipgloss v1.1.0
github.com/cheggaaa/pb/v3 v3.1.7
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
github.com/chromedp/chromedp v0.14.1
@@ -46,24 +48,24 @@ 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.43.0
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
golang.org/x/net v0.46.0
golang.org/x/sys v0.37.0
golang.org/x/term v0.36.0
golang.org/x/text v0.30.0
golang.org/x/mod v0.30.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.39.0
golang.org/x/term v0.38.0
golang.org/x/text v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
gopkg.in/AlecAivazis/survey.v1 v1.8.8
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
modernc.org/sqlite v1.39.0
tailscale.com v1.88.4
tailscale.com v1.90.6
)
require (
@@ -75,24 +77,23 @@ 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/aws/aws-sdk-go-v2 v1.39.2 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/carapace-sh/carapace v1.9.0 // indirect
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
@@ -100,15 +101,14 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gaissmai/bart v0.25.0 // indirect
github.com/gen2brain/shm v0.1.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
@@ -120,7 +120,6 @@ require (
github.com/google/nftables v0.3.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v3 v3.0.2 // indirect
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
@@ -132,17 +131,20 @@ 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 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.8.0 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/moloch--/go-keystone v0.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
@@ -156,19 +158,18 @@ require (
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/tools v0.39.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-20251014184007-4626949a642f // indirect
+75 -58
View File
@@ -11,9 +11,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
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=
@@ -29,13 +26,12 @@ github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRl
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
github.com/VirusTotal/vt-go v1.0.1 h1:rj/qugIY8XNC6ogwOaeJAGCOsb3nmY63+yuDMtHOx0Q=
github.com/VirusTotal/vt-go v1.0.1/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
@@ -50,6 +46,8 @@ github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktp
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
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=
@@ -82,12 +80,48 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3 h1:KUeWGoKnmyrLaDIa0smE6pK5eFMZWNIxPGweQR12iLg=
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3/go.mod h1:OMqKat/mm9a/qOnpuNOPyYO9bPzRNnmzLnRZT5KYltg=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
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/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d h1:ZtA1sedVbEW7EW80Iz2GR3Ye6PwbJAJXjv7D74xG6HU=
@@ -110,7 +144,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -134,6 +167,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
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=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
@@ -212,9 +247,6 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -261,7 +293,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -271,16 +302,18 @@ github.com/lesnuages/go-winio v0.4.19 h1:lFDu1mnhg5em+8zTHO4ChSD11J56xkyE8m3N8Ir
github.com/lesnuages/go-winio v0.4.19/go.mod h1:rm7jf4kBcldxMeljR7c7XY1qVCBc+8z3PtSgkrT9Clk=
github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193U=
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
@@ -294,13 +327,12 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
@@ -309,6 +341,12 @@ github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxc
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-sqlite3 v0.29.1 h1:NIi8AISWBToRHyoz01FXiTNvU147Tqdibgj2tFzJCqM=
@@ -372,7 +410,6 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -416,18 +453,18 @@ github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yiya1989/sshkrb5 v0.0.1 h1:oAKpXLBeZExjYDV9eTL2KypmXU+5oPrJtqDuR5JbWic=
github.com/yiya1989/sshkrb5 v0.0.1/go.mod h1:r1bHcNa22x8rrg/Kj/cDF5bX6UtZrHiQgRFvj3Mnpl8=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
@@ -449,13 +486,11 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
@@ -469,9 +504,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
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=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -480,10 +514,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
@@ -493,46 +525,34 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.0.0-20220722155255-886fb9371eb4/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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -544,9 +564,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
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=
@@ -577,8 +596,6 @@ google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -645,5 +662,5 @@ mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.88.4 h1:fXWotRMi9ZARyHRdKQa4ohXj8kqtemvvTzjreWLHVHo=
tailscale.com v1.88.4/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw=
tailscale.com v1.90.6 h1:EhYPiZP/xcLeinikaLA0kn4CQT3+z9SZ13IB/kzWhd4=
tailscale.com v1.90.6/go.mod h1:+9EX6pOGCNa6pxCVRhhlJLy/qnkDzOplFYpeZyYlCT0=
@@ -37,17 +37,6 @@ package dnsclient
Stop = 4 bytes max
Size = 4 bytes max
Data = 78 - (2+4+4+4+4) ~= 60 bytes per query worst case
*** BASE58 ***
Base58 ~2% less efficient than Base64, but we can't use all 64 chars in DNS so
it's just not an option, we could potentially use some type of Base62 encoding
but those implementations are more complex and only marginally more efficient
than Base58, and Base58 avoids any complexities with '-' in domain names.
The idea is that since the server returns the messages CRC32 checksum we can detect
when the message is transparently corrupted by some rude resolver. So when we init
the session we send a few messages with random data to see if we can use Base58, and
fallback to Base32 if we detect problems.
*/
// {{if .Config.IncludeDNS}}
@@ -102,7 +91,6 @@ type DNSOptions struct {
RetryCount int
MaxErrors int
WorkersPerResolver int
ForceBase32 bool
NoTXT bool
ForceResolvConf string
ForceResolvers string
@@ -142,7 +130,6 @@ func ParseDNSOptions(c2URI *url.URL) *DNSOptions {
RetryCount: retryCount,
MaxErrors: maxErrors,
WorkersPerResolver: workersPerResolver,
ForceBase32: strings.ToLower(c2URI.Query().Get("force-base32")) == "true",
NoTXT: strings.ToLower(c2URI.Query().Get("notxt")) == "true",
ForceResolvConf: c2URI.Query().Get("force-resolv-conf"),
ForceResolvers: c2URI.Query().Get("resolvers"),
@@ -169,7 +156,6 @@ func NewDNSClient(parent string, opts *DNSOptions) *SliverDNSClient {
return &SliverDNSClient{
metadata: map[string]*ResolverMetadata{},
parent: parent,
forceBase32: opts.ForceBase32,
noTXT: opts.NoTXT,
forceResolvConf: opts.ForceResolvConf,
forceResolvers: opts.ForceResolvers,
@@ -181,7 +167,6 @@ func NewDNSClient(parent string, opts *DNSOptions) *SliverDNSClient {
WorkersPerResolver: opts.WorkersPerResolver,
subdataSpace: 254 - len(parent) - (1 + (254-len(parent))/64),
base32: encoders.Base32Encoder{},
base58: encoders.Base58Encoder{},
}
}
@@ -195,7 +180,6 @@ type SliverDNSClient struct {
retryWait time.Duration
retryCount int
queryTimeout time.Duration
forceBase32 bool
noTXT bool
forceResolvConf string
forceResolvers string
@@ -211,9 +195,6 @@ type SliverDNSClient struct {
WorkersPerResolver int
base32 encoders.Base32Encoder
base58 encoders.Base58Encoder
enableCaseSensitiveEncoder bool
}
// DNSWork - Single unit of work for DNSWorker
@@ -278,10 +259,9 @@ func (w *DNSWorker) Start(id int, recvQueue <-chan *DNSWork, sendQueue <-chan *D
// ResolverMetadata - Metadata for the resolver
type ResolverMetadata struct {
Address string
EnableBase58 bool
Metrics []time.Duration
Errors int
Address string
Metrics []time.Duration
Errors int
}
// SessionInit - Initialize DNS session
@@ -328,19 +308,13 @@ func (s *SliverDNSClient) SessionInit() error {
// {{end}}
return err
}
resolver, meta := s.randomResolver()
var encoder encoders.Encoder
if meta.EnableBase58 {
encoder = s.base58
} else {
encoder = s.base32
}
resolver, _ := s.randomResolver()
initMsg := &dnspb.DNSMessage{
ID: s.nextMsgID(),
Type: dnspb.DNSMessageType_INIT,
Size: uint32(len(initData)),
}
respData, err := s.sendInit(resolver, encoder, initMsg, initData)
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[dns] init msg send failure %v", err)
@@ -403,7 +377,8 @@ func (s *SliverDNSClient) sendInit(resolver DNSResolver, encoder encoders.Encode
return nil, err
}
resp := []byte{}
for _, subdata := range allSubdata {
receivedResponse := false
for index, subdata := range allSubdata {
var respData []byte
var err error
@@ -419,14 +394,25 @@ func (s *SliverDNSClient) sendInit(resolver DNSResolver, encoder encoders.Encode
return nil, err
}
if 0 < len(respData) {
resp = append(resp, respData...)
} else {
if len(allSubdata) == 1 {
resp = append(resp, respData...)
} else {
resp = respData
}
receivedResponse = true
} else if len(allSubdata) == 1 || (index == len(allSubdata)-1 && !receivedResponse) {
// {{if .Config.Debug}}
log.Printf("[dns] no data received in response")
// {{end}}
return nil, ErrInvalidResponse
}
}
if !receivedResponse {
// {{if .Config.Debug}}
log.Printf("[dns] no data received in response")
// {{end}}
return nil, ErrInvalidResponse
}
return resp, nil
}
@@ -460,8 +446,8 @@ func (s *SliverDNSClient) ReadEnvelope() (*pb.Envelope, error) {
log.Printf("[dns] read envelope ...")
// {{end}}
resolver, meta := s.randomResolver()
pollMsg, err := s.pollMsg(meta)
resolver, _ := s.randomResolver()
pollMsg, err := s.pollMsg()
if err != nil {
return nil, err
}
@@ -561,19 +547,13 @@ func (s *SliverDNSClient) CloseSession() error {
// parallelSend - send a full message to teh server
func (s *SliverDNSClient) parallelSend(data []byte) error {
var encoder encoders.Encoder
if s.enableCaseSensitiveEncoder {
encoder = s.base58
} else {
encoder = s.base32
}
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT,
ID: s.nextMsgID(),
Size: uint32(len(data)),
}
domains, err := s.SplitBuffer(msg, encoder, data)
domains, err := s.SplitBuffer(msg, s.base32, data)
if err != nil {
return err
}
@@ -889,7 +869,7 @@ func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
return domain, nil
}
func (s *SliverDNSClient) pollMsg(meta *ResolverMetadata) (string, error) {
func (s *SliverDNSClient) pollMsg() (string, error) {
nonceBuf := make([]byte, 8)
rand.Read(nonceBuf)
pollMsg, _ := proto.Marshal(&dnspb.DNSMessage{
@@ -897,13 +877,8 @@ func (s *SliverDNSClient) pollMsg(meta *ResolverMetadata) (string, error) {
Type: dnspb.DNSMessageType_POLL,
Data: nonceBuf,
})
if s.enableCaseSensitiveEncoder {
msg, _ := s.base58.Encode(pollMsg)
return string(msg), nil
} else {
msg, _ := s.base32.Encode(pollMsg)
return string(msg), nil
}
msg, _ := s.base32.Encode(pollMsg)
return string(msg), nil
}
func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
@@ -914,13 +889,8 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
Type: dnspb.DNSMessageType_CLEAR,
Data: nonceBuf,
})
if s.enableCaseSensitiveEncoder {
msg, _ := s.base58.Encode(clearMsg)
return string(msg), nil
} else {
msg, _ := s.base32.Encode(clearMsg)
return string(msg), nil
}
msg, _ := s.base32.Encode(clearMsg)
return string(msg), nil
}
func (s *SliverDNSClient) otpMsg() (string, error) {
@@ -936,7 +906,7 @@ func (s *SliverDNSClient) otpMsg() (string, error) {
return string(msg), nil
}
// fingerprintResolver - Fingerprints resolve to determine if we can use a case sensitive encoding
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
func (s *SliverDNSClient) fingerprintResolvers() {
wg := &sync.WaitGroup{}
// {{if .Config.Debug}}
@@ -960,15 +930,14 @@ func (s *SliverDNSClient) fingerprintResolvers() {
// {{if .Config.Debug}}
for _, result := range s.metadata {
log.Printf("[dns] %s: avg rtt %s, base58: %v, errors %d",
result.Address, s.averageRtt(result), result.EnableBase58, result.Errors)
log.Printf("[dns] %s: avg rtt %s, errors %d",
result.Address, s.averageRtt(result), result.Errors)
}
// {{end}}
// NOTE: In the future we may want to add a configurable error threshold for now
// if we encounter any errors we don't use the resolver.
workingResolvers := []DNSResolver{}
allSupportBase58 := true
for _, resolver := range s.resolvers {
meta := s.metadata[resolver.Address()]
if 0 < meta.Errors {
@@ -977,41 +946,24 @@ func (s *SliverDNSClient) fingerprintResolvers() {
// {{end}}
continue
}
if !meta.EnableBase58 {
allSupportBase58 = false
}
workingResolvers = append(workingResolvers, resolver)
}
if allSupportBase58 && !s.forceBase32 {
s.enableCaseSensitiveEncoder = true
}
s.resolvers = workingResolvers
}
// Fingerprints a single resolver to determine if we can use a case sensitive encoding, average
// round trip time, and if it works at all
// Fingerprints a single resolver to measure RTT and validate base32 transport.
func (s *SliverDNSClient) fingerprintResolver(id int, wg *sync.WaitGroup, results chan<- *ResolverMetadata, resolver DNSResolver) {
defer wg.Done()
meta := &ResolverMetadata{
Address: resolver.Address(),
EnableBase58: false,
Metrics: []time.Duration{},
Errors: 0,
}
s.benchmark(id, s.base32, resolver, meta)
if meta.Errors == 0 && !s.forceBase32 {
s.benchmark(id, s.base58, resolver, meta)
if meta.Errors == 0 {
meta.EnableBase58 = true
} else {
meta.EnableBase58 = false
meta.Errors = 0 // Reset base32 error count
}
Address: resolver.Address(),
Metrics: []time.Duration{},
Errors: 0,
}
s.benchmark(id, resolver, meta)
results <- meta
}
func (s *SliverDNSClient) benchmark(id int, encoder encoders.Encoder, resolver DNSResolver, meta *ResolverMetadata) {
func (s *SliverDNSClient) benchmark(id int, resolver DNSResolver, meta *ResolverMetadata) {
for index := 0; index < metricsMaxSize/2; index++ {
finger, fingerChecksum, err := s.fingerprintMsg(id)
if err != nil {
@@ -1021,7 +973,7 @@ func (s *SliverDNSClient) benchmark(id int, encoder encoders.Encoder, resolver D
// {{end}}
continue
}
encodedValue, _ := encoder.Encode(finger)
encodedValue, _ := s.base32.Encode(finger)
domain, err := s.joinSubdataToParent(string(encodedValue))
if err != nil {
meta.Errors++
@@ -36,9 +36,10 @@ import (
const (
// Do not change these without updating the tests
parent1 = ".1.example.com."
parent2 = ".something-longer.example.com."
parent3 = ".something-even-longer.example.computer."
parent1 = ".1.example.com."
parent2 = ".something-longer.example.com."
parent3 = ".something-even-longer.example.computer."
base32Alphabet = "ab1c2d3e4f5g6h7j8k9m0npqrtuvwxyz"
)
var (
@@ -70,35 +71,39 @@ func randomData(size int) []byte {
return buf
}
func TestSplitBufferBase58(t *testing.T) {
t.Logf("Testing with client1")
client1 := NewDNSClient(parent1, opts)
testData := randomData(2048)
clientSplitBuffer(t, client1, encoders.Base58{}, testData)
t.Logf("Testing with client2")
client2 := NewDNSClient(parent2, opts)
testData2 := randomData(2048)
clientSplitBuffer(t, client2, encoders.Base58{}, testData2)
t.Logf("Testing with client3")
client3 := NewDNSClient(parent3, opts)
testData3 := randomData(2048)
clientSplitBuffer(t, client3, encoders.Base58{}, testData3)
t.Logf("Testing with client max")
clientMax := NewDNSClient(parentMax, opts)
testDataMax := randomData(2048)
clientSplitBuffer(t, clientMax, encoders.Base58{}, testDataMax)
t.Logf("Testing all clients with randomly sized data")
for _, client := range []*SliverDNSClient{client1, client2, client3} {
for count := 0; count < 10; count++ {
testData := randomDataRandomSize(2 * 1024 * 1024)
clientSplitBuffer(t, client, encoders.Base58{}, testData)
func isBase32String(value string) bool {
for i := 0; i < len(value); i++ {
if strings.IndexByte(base32Alphabet, value[i]) == -1 {
return false
}
}
return true
}
type initSequenceResolver struct {
responses [][]byte
index int
}
func (r *initSequenceResolver) Address() string {
return "init-sequence"
}
func (r *initSequenceResolver) A(domain string) ([]byte, time.Duration, error) {
return nil, 0, nil
}
func (r *initSequenceResolver) AAAA(domain string) ([]byte, time.Duration, error) {
return nil, 0, nil
}
func (r *initSequenceResolver) TXT(domain string) ([]byte, time.Duration, error) {
if r.index >= len(r.responses) {
return nil, 0, nil
}
resp := r.responses[r.index]
r.index++
return resp, 0, nil
}
func TestSplitBufferBase32(t *testing.T) {
@@ -132,6 +137,69 @@ func TestSplitBufferBase32(t *testing.T) {
}
}
func TestSplitBufferDomainConstraints(t *testing.T) {
client := NewDNSClient(parent1, opts)
testData := randomData(8192)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT,
Size: uint32(len(testData)),
}
domains, err := client.SplitBuffer(msg, encoders.Base32{}, testData)
if err != nil {
t.Fatalf("Unexpected error splitting buffer: %s", err)
}
for _, domain := range domains {
if !strings.HasSuffix(domain, parent1) {
t.Fatalf("Domain missing parent suffix: %s", domain)
}
if len(domain) > 254 {
t.Fatalf("Domain exceeds DNS length limit: %d", len(domain))
}
subdata := strings.TrimSuffix(domain, parent1)
if !isBase32String(strings.ReplaceAll(subdata, ".", "")) {
t.Fatalf("Subdata contains non-base32 characters: %s", subdata)
}
for _, label := range strings.Split(subdata, ".") {
if len(label) > 63 {
t.Fatalf("Label exceeds 63 chars: %s", label)
}
}
}
}
func TestSendInitAllowsEmptyResponses(t *testing.T) {
client := NewDNSClient(parent1, opts)
testData := randomData(4096)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 1,
Size: uint32(len(testData)),
}
domains, err := client.SplitBuffer(msg, encoders.Base32{}, testData)
if err != nil {
t.Fatalf("Unexpected error splitting buffer: %s", err)
}
if len(domains) < 2 {
t.Fatalf("expected multiple subdata domains, got %d", len(domains))
}
expected := []byte{1, 2, 3, 4}
responses := make([][]byte, len(domains))
responses[len(responses)-1] = expected
resolver := &initSequenceResolver{responses: responses}
resp, err := client.sendInit(resolver, encoders.Base32{}, msg, testData)
if err != nil {
t.Fatalf("sendInit failed: %s", err)
}
if !bytes.Equal(resp, expected) {
t.Fatalf("unexpected init response: %v", resp)
}
if resolver.index != len(domains) {
t.Fatalf("expected %d resolver calls, got %d", len(domains), resolver.index)
}
}
func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.Encoder, testData []byte) {
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT,
@@ -162,25 +230,74 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
}
}
// func addTestResolver(client *SliverDNSClient, enableBase58 bool) {
// client.resolvers = []DNSResolver{
// &GenericResolver{
// address: "127.0.0.1:53",
// retries: 1,
// retryWait: retryWait,
// resolver: &dns.Client{
// ReadTimeout: timeout,
// WriteTimeout: timeout,
// },
// base64: encoders.Base64{},
// },
// }
// client.metadata["127.0.0.1:53"] = &ResolverMetadata{
// Address: "127.0.0.1:53",
// EnableBase58: enableBase58,
// Errors: 0,
// }
// }
func TestOTPMsgRoundTrip(t *testing.T) {
client := NewDNSClient(parent1, opts)
encoded, err := client.otpMsg()
if err != nil {
t.Fatalf("Unexpected otpMsg error: %s", err)
}
if !isBase32String(encoded) {
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
}
raw, err := encoders.Base32{}.Decode([]byte(encoded))
if err != nil {
t.Fatalf("Unexpected otpMsg decode error: %s", err)
}
msg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(raw, msg); err != nil {
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
}
if msg.Type != dnspb.DNSMessageType_TOTP {
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
}
if msg.ID != 0 {
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
}
}
func TestPollAndClearMsgRoundTrip(t *testing.T) {
client := NewDNSClient(parent1, opts)
client.dnsSessionID = 42
pollEncoded, err := client.pollMsg()
if err != nil {
t.Fatalf("Unexpected pollMsg error: %s", err)
}
clearEncoded, err := client.clearMsg(99)
if err != nil {
t.Fatalf("Unexpected clearMsg error: %s", err)
}
for _, test := range []struct {
name string
encoded string
wantType dnspb.DNSMessageType
wantID uint32
wantDataSz int
}{
{name: "poll", encoded: pollEncoded, wantType: dnspb.DNSMessageType_POLL, wantID: 42, wantDataSz: 8},
{name: "clear", encoded: clearEncoded, wantType: dnspb.DNSMessageType_CLEAR, wantID: 99, wantDataSz: 8},
} {
if !isBase32String(test.encoded) {
t.Fatalf("%s contains non-base32 characters: %s", test.name, test.encoded)
}
raw, err := encoders.Base32{}.Decode([]byte(test.encoded))
if err != nil {
t.Fatalf("%s decode error: %s", test.name, err)
}
msg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(raw, msg); err != nil {
t.Fatalf("%s unmarshal error: %s", test.name, err)
}
if msg.Type != test.wantType {
t.Fatalf("%s type mismatch: %v", test.name, msg.Type)
}
if msg.ID != test.wantID {
t.Fatalf("%s id mismatch: %d", test.name, msg.ID)
}
if len(msg.Data) != test.wantDataSz {
t.Fatalf("%s data length mismatch: %d", test.name, len(msg.Data))
}
}
}
func TestSubdataSpace(t *testing.T) {
+1 -13
View File
@@ -252,19 +252,7 @@ func setupGo(appDir string) error {
return err
}
return setupSGN(appDir)
}
func setupSGN(appDir string) error {
goBinPath := filepath.Join(appDir, "go", "bin")
sgnZipFSPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, "sgn.zip")
sgnZip, err := assetsFs.ReadFile(sgnZipFSPath)
if err != nil {
setupLog.Errorf("static asset not found: %s", sgnZipFSPath)
return err
}
_, err = unzipBuf(sgnZip, goBinPath)
return err
return nil
}
// SetupGoPath - Extracts dependencies to goPathSrc
+1 -1
View File
@@ -28,8 +28,8 @@ import (
"path/filepath"
"strings"
ver "github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/server/log"
ver "github.com/bishopfox/sliver/server/version"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
+58 -25
View File
@@ -45,7 +45,6 @@ import (
"strings"
"sync"
"time"
"unicode"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/dnspb"
@@ -419,32 +418,17 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
// checksum calculation here to ensure that no one accidentally calculates the crc32
// of the plaintext data (that would be very bad).
func (s *SliverDNSServer) decodeSubdata(subdomain string) (*dnspb.DNSMessage, uint32, error) {
subdata := strings.Join(strings.Split(subdomain, "."), "")
subdata := strings.ToLower(strings.Join(strings.Split(subdomain, "."), ""))
dnsLog.Debugf("subdata = %s", subdata)
encoders := s.determineLikelyEncoders(subdata)
for _, encoder := range encoders {
data, err := encoder.Decode([]byte(subdata))
if err == nil {
msg := &dnspb.DNSMessage{}
err = proto.Unmarshal(data, msg)
if err == nil {
return msg, crc32.ChecksumIEEE(data), nil
}
}
dnsLog.Debugf("failed to decode subdata with %#v (%s)", encoder, err)
data, err := encoders.Base32{}.Decode([]byte(subdata))
if err != nil {
return nil, 0, ErrInvalidMsg
}
return nil, 0, ErrInvalidMsg
}
// Returns the most likely -> least likely encoders, if decoding fails fallback to
// the next encoder until we run out of options.
func (s *SliverDNSServer) determineLikelyEncoders(subdata string) []encoders.Encoder {
for _, char := range subdata {
if unicode.IsUpper(char) {
return []encoders.Encoder{encoders.Base58{}, encoders.Base32{}}
}
msg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(data, msg); err != nil {
return nil, 0, ErrInvalidMsg
}
return []encoders.Encoder{encoders.Base32{}, encoders.Base58{}}
return msg, crc32.ChecksumIEEE(data), nil
}
func (s *SliverDNSServer) nameErrorResp(req *dns.Msg) *dns.Msg {
@@ -454,6 +438,13 @@ func (s *SliverDNSServer) nameErrorResp(req *dns.Msg) *dns.Msg {
return resp
}
func (s *SliverDNSServer) emptySuccessResp(req *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(req)
resp.Authoritative = true
return resp
}
func (s *SliverDNSServer) refusedErrorResp(req *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetRcode(req, dns.RcodeRefused)
@@ -461,6 +452,38 @@ func (s *SliverDNSServer) refusedErrorResp(req *dns.Msg) *dns.Msg {
return resp
}
func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, bool, error) {
if msg.Size == 0 {
return nil, false, ErrInvalidMsg
}
pendingValue, loaded := s.messages.Load(msg.ID)
if !loaded {
pendingValue = &PendingEnvelope{
Size: msg.Size,
received: uint32(0),
messages: map[uint32][]byte{},
mutex: &sync.Mutex{},
complete: false,
}
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
pendingValue = actual
}
pending := pendingValue.(*PendingEnvelope)
if pending.Size != msg.Size && msg.Size != 0 {
s.messages.Delete(msg.ID)
return nil, false, ErrInvalidMsg
}
if !pending.Insert(msg) {
return nil, false, nil
}
data, err := pending.Reassemble()
s.messages.Delete(msg.ID)
if err != nil {
return nil, false, err
}
return data, true, nil
}
// ---------------------------
// DNS Message Handlers
// ---------------------------
@@ -502,7 +525,7 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
if len(msg.Data) <= 32 {
if msg.Size <= 32 {
dnsLog.Warnf("[session init] invalid msg data length")
return s.refusedErrorResp(req)
}
@@ -511,6 +534,16 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
return s.refusedErrorResp(req)
}
initData, complete, err := s.accumulateInitData(msg)
if err != nil {
dnsLog.Errorf("[session init] error reassembling init data: %s", err)
return s.refusedErrorResp(req)
}
if !complete {
return s.emptySuccessResp(req)
}
msg.Data = initData
var publicKeyDigest [32]byte
copy(publicKeyDigest[:], msg.Data[:32])
implantBuild, err := db.ImplantBuildByPublicKeyDigest(publicKeyDigest)
+696 -9
View File
@@ -21,16 +21,24 @@ package c2
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"hash/crc32"
insecureRand "math/rand"
"strings"
"sync"
"testing"
"time"
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
"github.com/bishopfox/sliver/protobuf/dnspb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/core"
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/util/encoders"
"github.com/miekg/dns"
"google.golang.org/protobuf/proto"
)
@@ -61,6 +69,93 @@ func shuffleDNSMsgs(a []*dnspb.DNSMessage) {
}
}
type testDNSResolver struct {
server *SliverDNSServer
parent string
base64 implantEncoders.Base64Encoder
}
func (r *testDNSResolver) Address() string {
return "test-resolver"
}
func (r *testDNSResolver) A(domain string) ([]byte, time.Duration, error) {
resp, err := r.exchange(domain, dns.TypeA)
if err != nil {
return nil, 0, err
}
records := []byte{}
for _, answer := range resp.Answer {
if a, ok := answer.(*dns.A); ok {
records = append(records, []byte(a.A)...)
}
}
return records, 0, nil
}
func (r *testDNSResolver) AAAA(domain string) ([]byte, time.Duration, error) {
resp, err := r.exchange(domain, dns.TypeAAAA)
if err != nil {
return nil, 0, err
}
records := make([]byte, 512)
dataSize := uint32(0)
for _, answer := range resp.Answer {
if aaaa, ok := answer.(*dns.AAAA); ok {
chunkMeta := uint32(aaaa.Hdr.Ttl)
chunkIdx := (chunkMeta & 0xff00) >> 8
tempSize := chunkMeta & 0xff
if dataSize != 0 && tempSize != dataSize {
return nil, 0, dnsclient.ErrInvalidResponse
}
if dataSize == 0 {
dataSize = tempSize
}
copy(records[chunkIdx*16:], []byte(aaaa.AAAA))
}
}
if dataSize == 0 {
return nil, 0, nil
}
data := make([]byte, dataSize)
copy(data, records[:dataSize])
return data, 0, nil
}
func (r *testDNSResolver) TXT(domain string) ([]byte, time.Duration, error) {
resp, err := r.exchange(domain, dns.TypeTXT)
if err != nil {
return nil, 0, err
}
records := ""
for _, answer := range resp.Answer {
if txt, ok := answer.(*dns.TXT); ok {
records += strings.Join(txt.Txt, "")
}
}
if records == "" {
return nil, 0, nil
}
data, err := r.base64.Decode([]byte(records))
if err != nil {
return nil, 0, err
}
return data, 0, nil
}
func (r *testDNSResolver) exchange(domain string, qtype uint16) (*dns.Msg, error) {
req := new(dns.Msg)
req.SetQuestion(domain, qtype)
resp := r.server.handleC2(r.parent, req)
if resp == nil {
return nil, dnsclient.ErrInvalidResponse
}
if resp.Rcode != dns.RcodeSuccess {
return resp, dnsclient.ErrInvalidResponse
}
return resp, nil
}
func randomDNSMsgs(t *testing.T, parent string, maxSize int, encoder encoders.Encoder, client *dnsclient.SliverDNSClient) ([]*dnspb.DNSMessage, []byte) {
testData := randomDataRandomSize(maxSize)
dnsMsgs := []*dnspb.DNSMessage{}
@@ -93,12 +188,10 @@ func randomDNSMsgs(t *testing.T, parent string, maxSize int, encoder encoders.En
func TestPendingEnvelopes(t *testing.T) {
// *** Small ***
for i := 0; i < 100; i++ {
reassemble(t, example1, 256, encoders.Base58{})
reassemble(t, example1, 256, encoders.Base32{})
}
// *** Large ***
for i := 0; i < 100; i++ {
reassemble(t, example1, 30*1024, encoders.Base58{})
reassemble(t, example1, 30*1024, encoders.Base32{})
}
}
@@ -139,6 +232,194 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
}
}
func TestDNSHelloRoundTrip(t *testing.T) {
server := newTestDNSServer()
resolver := &testDNSResolver{server: server, parent: example1}
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_TOTP,
ID: 0,
}
domain, err := encodeMessageDomain(example1, msg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
data, _, err := resolver.A(domain)
if err != nil {
t.Fatalf("resolver.A failed: %s", err)
}
if len(data) != 4 {
t.Fatalf("expected 4 bytes in response, got %d", len(data))
}
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
if sessionID == 0 {
t.Fatal("expected non-zero session id")
}
if _, ok := server.sessions.Load(sessionID); !ok {
t.Fatalf("expected session %d to be stored", sessionID)
}
}
func TestDNSNOPRoundTripChecksum(t *testing.T) {
server := newTestDNSServer()
sessionID := uint32(0x123456)
dnsSession := newTestDNSSession(sessionID)
server.sessions.Store(sessionID, dnsSession)
payload := make([]byte, 16)
rand.Read(payload)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_NOP,
ID: (1 << 24) | sessionID,
Data: payload,
}
raw, err := proto.Marshal(msg)
if err != nil {
t.Fatalf("marshal failed: %s", err)
}
domain, err := encodeMessageDomain(example1, msg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
resolver := &testDNSResolver{server: server, parent: example1}
data, _, err := resolver.A(domain)
if err != nil {
t.Fatalf("resolver.A failed: %s", err)
}
if len(data) != 4 {
t.Fatalf("expected 4 bytes in response, got %d", len(data))
}
got := binary.LittleEndian.Uint32(data)
want := crc32.ChecksumIEEE(raw)
if got != want {
t.Fatalf("checksum mismatch: got %d want %d", got, want)
}
}
func TestDNSPollManifestReuse(t *testing.T) {
server := newTestDNSServer()
sessionID := uint32(0x654321)
dnsSession := newTestDNSSession(sessionID)
server.sessions.Store(sessionID, dnsSession)
resolver := &testDNSResolver{server: server, parent: example1}
pollMsg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_POLL,
ID: sessionID,
Data: []byte{1, 2, 3, 4, 5, 6, 7, 8},
}
pollDomain, err := encodeMessageDomain(example1, pollMsg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
respData, _, err := resolver.TXT(pollDomain)
if err != nil {
t.Fatalf("resolver.TXT failed: %s", err)
}
manifest := &dnspb.DNSMessage{}
if err := proto.Unmarshal(respData, manifest); err != nil {
t.Fatalf("manifest unmarshal failed: %s", err)
}
if manifest.Type != dnspb.DNSMessageType_MANIFEST || manifest.Size != 0 || manifest.ID != 0 {
t.Fatalf("unexpected empty manifest: %+v", manifest)
}
msgID := uint32(0x01000000 | sessionID)
dnsSession.outgoingMsgIDs = append(dnsSession.outgoingMsgIDs, msgID)
dnsSession.outgoingBuffers[msgID] = bytes.Repeat([]byte{0xAB}, 32)
respData, _, err = resolver.TXT(pollDomain)
if err != nil {
t.Fatalf("resolver.TXT failed: %s", err)
}
manifest = &dnspb.DNSMessage{}
if err := proto.Unmarshal(respData, manifest); err != nil {
t.Fatalf("manifest unmarshal failed: %s", err)
}
if manifest.Type != dnspb.DNSMessageType_MANIFEST || manifest.ID != msgID || manifest.Size != 32 {
t.Fatalf("unexpected manifest: %+v", manifest)
}
if dnsSession.dnsIdMsgIdMap[pollMsg.ID] != msgID {
t.Fatalf("expected dnsIdMsgIdMap to track msg id %d", msgID)
}
respData, _, err = resolver.TXT(pollDomain)
if err != nil {
t.Fatalf("resolver.TXT failed: %s", err)
}
manifest = &dnspb.DNSMessage{}
if err := proto.Unmarshal(respData, manifest); err != nil {
t.Fatalf("manifest unmarshal failed: %s", err)
}
if manifest.Type != dnspb.DNSMessageType_MANIFEST || manifest.ID != msgID || manifest.Size != 32 {
t.Fatalf("unexpected reused manifest: %+v", manifest)
}
}
func TestDNSReadEnvelopeRoundTripTXT(t *testing.T) {
runDNSReadEnvelopeRoundTrip(t, false)
}
func TestDNSReadEnvelopeRoundTripAAAA(t *testing.T) {
runDNSReadEnvelopeRoundTrip(t, true)
}
func TestDNSWriteEnvelopeRoundTrip(t *testing.T) {
server := newTestDNSServer()
sessionID := uint32(0x102030)
key := implantCrypto.RandomSymmetricKey()
dnsSession := newTestDNSSession(sessionID)
dnsSession.CipherCtx = serverCrypto.NewCipherContext(key)
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
respCh := make(chan *sliverpb.Envelope, 1)
dnsSession.ImplantConn.Resp[77] = respCh
server.sessions.Store(sessionID, dnsSession)
resolver := &testDNSResolver{server: server, parent: example1}
envelope := &sliverpb.Envelope{
ID: 77,
Type: sliverpb.MsgPing,
Data: []byte("hello"),
}
sendEnvelopeViaDNS(t, resolver, sessionID, key, envelope)
select {
case got := <-respCh:
if !proto.Equal(got, envelope) {
t.Fatalf("envelope mismatch: %#v != %#v", got, envelope)
}
case <-time.After(2 * time.Second):
t.Fatal("timeout waiting for envelope")
}
}
func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
server := newTestDNSServer()
sessionID := uint32(0x121212)
dnsSession := newTestDNSSession(sessionID)
dnsSession.outgoingBuffers[0x01000000|sessionID] = bytes.Repeat([]byte{0xCD}, 16)
server.sessions.Store(sessionID, dnsSession)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_DATA_TO_IMPLANT,
ID: 0x01000000 | sessionID,
Start: 5,
Stop: 4,
}
domain, err := encodeMessageDomain(example1, msg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
req := new(dns.Msg)
req.SetQuestion(domain, dns.TypeTXT)
resp := server.handleC2(example1, req)
if resp == nil || resp.Rcode != dns.RcodeRefused {
t.Fatalf("expected refused response, got %+v", resp)
}
}
func TestIsC2Domain(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
@@ -162,14 +443,420 @@ func TestIsC2Domain(t *testing.T) {
}
}
func TestDetermineLikelyEncoders(t *testing.T) {
func TestDecodeSubdataBase32(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
sample := randomDataRandomSize(2048)
encodedSample, _ := encoders.Base58{}.Encode(sample)
b58Sample := string(encodedSample)
likelyEncoders := listener.determineLikelyEncoders(b58Sample)
_, err := likelyEncoders[0].Decode([]byte(b58Sample))
payload := make([]byte, 64)
rand.Read(payload)
original := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_NOP,
ID: 42,
Data: payload,
}
raw, err := proto.Marshal(original)
if err != nil {
t.Error("DetermineLikelyEncoders failed to decode sample")
t.Fatalf("failed to marshal message: %s", err)
}
encoded, _ := encoders.Base32{}.Encode(raw)
if len(encoded) < 12 {
t.Fatalf("encoded sample too short: %d", len(encoded))
}
subdomain := fmt.Sprintf("%s.%s", string(encoded[:12]), string(encoded[12:]))
for _, sample := range []string{subdomain, strings.ToUpper(subdomain)} {
msg, checksum, err := listener.decodeSubdata(sample)
if err != nil {
t.Fatalf("decodeSubdata failed: %s", err)
}
if !proto.Equal(msg, original) {
t.Fatalf("decoded message mismatch: %#v != %#v", msg, original)
}
if checksum != crc32.ChecksumIEEE(raw) {
t.Fatalf("unexpected checksum: %d", checksum)
}
}
}
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
_, _, err := listener.decodeSubdata("invalid")
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg, got %v", err)
}
}
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
if err != nil {
t.Fatalf("encode failed: %s", err)
}
_, _, err = listener.decodeSubdata(string(encoded))
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg, got %v", err)
}
}
func TestHandleC2RejectsUnknownSession(t *testing.T) {
server := newTestDNSServer()
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_NOP,
ID: 0x00123456,
Data: []byte{1, 2, 3},
}
domain, err := encodeMessageDomain(example1, msg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
req := new(dns.Msg)
req.SetQuestion(domain, dns.TypeA)
resp := server.handleC2(example1, req)
if resp == nil || resp.Rcode != dns.RcodeNameError {
t.Fatalf("expected name error response, got %+v", resp)
}
}
func TestAccumulateInitDataReassembles(t *testing.T) {
server := newTestDNSServer()
client := dnsclient.NewDNSClient(example1, opts)
testData := make([]byte, 2048)
rand.Read(testData)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x01020304,
Size: uint32(len(testData)),
}
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
if err != nil {
t.Fatalf("SplitBuffer failed: %s", err)
}
if len(domains) < 2 {
t.Fatalf("expected multiple init submessages, got %d", len(domains))
}
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
for _, domain := range domains {
subdata := strings.TrimSuffix(domain, example1)
subdata = strings.ReplaceAll(subdata, ".", "")
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
if err != nil {
t.Fatalf("decode subdata failed: %s", err)
}
subMsg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(raw, subMsg); err != nil {
t.Fatalf("unmarshal submsg failed: %s", err)
}
subMsgs = append(subMsgs, subMsg)
}
shuffleDNSMsgs(subMsgs)
var reassembled []byte
for _, subMsg := range subMsgs {
data, complete, err := server.accumulateInitData(subMsg)
if err != nil {
t.Fatalf("accumulateInitData failed: %s", err)
}
if !complete && data != nil {
t.Fatalf("unexpected data before completion")
}
if complete {
reassembled = data
}
}
if reassembled == nil {
t.Fatalf("expected reassembled init data")
}
if !bytes.Equal(reassembled, testData) {
t.Fatalf("reassembled init data mismatch")
}
}
func TestSplitToChunksPadding(t *testing.T) {
data := []byte{1, 2, 3, 4, 5, 6}
chunks := splitToChunks(data, 4)
if len(chunks) != 2 {
t.Fatalf("expected 2 chunks, got %d", len(chunks))
}
if !bytes.Equal(chunks[0], []byte{1, 2, 3, 4}) {
t.Fatalf("unexpected first chunk: %v", chunks[0])
}
if !bytes.Equal(chunks[1], []byte{5, 6, 0, 0}) {
t.Fatalf("unexpected second chunk: %v", chunks[1])
}
for _, chunk := range chunks {
if len(chunk) != 4 {
t.Fatalf("unexpected chunk length: %d", len(chunk))
}
}
}
func TestOutgoingReadBoundaries(t *testing.T) {
session := &DNSSession{
outgoingBuffers: map[uint32][]byte{
1: {1, 2, 3, 4},
},
outgoingMutex: &sync.RWMutex{},
}
if _, err := session.OutgoingRead(1, 0, 0); err == nil {
t.Fatal("expected error for stop <= start")
}
if _, err := session.OutgoingRead(1, 0, 5); err == nil {
t.Fatal("expected error for stop > length")
}
if _, err := session.OutgoingRead(1, 5, 6); err == nil {
t.Fatal("expected error for start > length")
}
if _, err := session.OutgoingRead(2, 0, 1); err == nil {
t.Fatal("expected error for missing buffer")
}
read, err := session.OutgoingRead(1, 1, 3)
if err != nil {
t.Fatalf("unexpected error for valid read: %s", err)
}
if !bytes.Equal(read, []byte{2, 3}) {
t.Fatalf("unexpected read data: %v", read)
}
}
func runDNSReadEnvelopeRoundTrip(t *testing.T, noTXT bool) {
t.Helper()
server := newTestDNSServer()
sessionID := uint32(0x2468AC)
key := implantCrypto.RandomSymmetricKey()
dnsSession := newTestDNSSession(sessionID)
dnsSession.CipherCtx = serverCrypto.NewCipherContext(key)
server.sessions.Store(sessionID, dnsSession)
envelope := &sliverpb.Envelope{
ID: 99,
Type: sliverpb.MsgPing,
Data: []byte("roundtrip"),
}
if err := dnsSession.StageOutgoingEnvelope(envelope); err != nil {
t.Fatalf("StageOutgoingEnvelope failed: %s", err)
}
if len(dnsSession.outgoingMsgIDs) != 1 {
t.Fatalf("expected 1 outgoing message id, got %d", len(dnsSession.outgoingMsgIDs))
}
msgID := dnsSession.outgoingMsgIDs[0]
resolver := &testDNSResolver{server: server, parent: example1}
got, err := readEnvelopeViaDNS(t, resolver, sessionID, key, noTXT)
if err != nil {
t.Fatalf("ReadEnvelope failed: %s", err)
}
if got == nil || !proto.Equal(got, envelope) {
t.Fatalf("envelope mismatch: %#v != %#v", got, envelope)
}
if _, ok := dnsSession.outgoingBuffers[msgID]; ok {
t.Fatalf("expected outgoing buffer %d to be cleared", msgID)
}
}
func newTestDNSServer() *SliverDNSServer {
return &SliverDNSServer{
sessions: &sync.Map{},
messages: &sync.Map{},
TTL: 0,
MaxTXTLength: defaultMaxTXTLength,
EnforceOTP: false,
}
}
func newTestDNSSession(sessionID uint32) *DNSSession {
return &DNSSession{
ID: sessionID,
dnsIdMsgIdMap: map[uint32]uint32{},
outgoingMsgIDs: []uint32{},
outgoingBuffers: map[uint32][]byte{},
outgoingMutex: &sync.RWMutex{},
incomingEnvelopes: map[uint32]*PendingEnvelope{},
incomingMutex: &sync.Mutex{},
msgCount: 0,
}
}
func encodeMessageDomain(parent string, msg *dnspb.DNSMessage) (string, error) {
raw, err := proto.Marshal(msg)
if err != nil {
return "", err
}
encoded, err := implantEncoders.Base32Encoder{}.Encode(raw)
if err != nil {
return "", err
}
return joinSubdataToParent(string(encoded), parent), nil
}
func joinSubdataToParent(subdata string, parent string) string {
subdomains := []string{}
for i := 0; i < len(subdata); i += 63 {
stop := i + 63
if len(subdata) < stop {
stop = len(subdata)
}
subdomains = append(subdomains, subdata[i:stop])
}
return strings.Join(subdomains, ".") + parent
}
func sendEnvelopeViaDNS(t *testing.T, resolver *testDNSResolver, sessionID uint32, key [32]byte, envelope *sliverpb.Envelope) {
t.Helper()
plaintext, err := proto.Marshal(envelope)
if err != nil {
t.Fatalf("marshal envelope failed: %s", err)
}
cipherCtx := implantCrypto.NewCipherContext(key)
ciphertext, err := cipherCtx.Encrypt(plaintext)
if err != nil {
t.Fatalf("encrypt envelope failed: %s", err)
}
client := dnsclient.NewDNSClient(example1, opts)
msgID := uint32(0x01000000 | sessionID)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT,
ID: msgID,
Size: uint32(len(ciphertext)),
}
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, ciphertext)
if err != nil {
t.Fatalf("SplitBuffer failed: %s", err)
}
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
for _, domain := range domains {
subdata := strings.TrimSuffix(domain, example1)
subdata = strings.ReplaceAll(subdata, ".", "")
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
if err != nil {
t.Fatalf("decode subdata failed: %s", err)
}
subMsg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(raw, subMsg); err != nil {
t.Fatalf("unmarshal submsg failed: %s", err)
}
subMsgs = append(subMsgs, subMsg)
}
shuffleDNSMsgs(subMsgs)
for _, subMsg := range subMsgs {
raw, err := proto.Marshal(subMsg)
if err != nil {
t.Fatalf("marshal submsg failed: %s", err)
}
domain, err := encodeMessageDomain(example1, subMsg)
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
}
data, _, err := resolver.A(domain)
if err != nil {
t.Fatalf("resolver.A failed: %s", err)
}
if len(data) != 4 {
t.Fatalf("expected 4-byte checksum, got %d", len(data))
}
got := binary.LittleEndian.Uint32(data)
want := crc32.ChecksumIEEE(raw)
if got != want {
t.Fatalf("checksum mismatch: got %d want %d", got, want)
}
}
}
func readEnvelopeViaDNS(t *testing.T, resolver *testDNSResolver, sessionID uint32, key [32]byte, noTXT bool) (*sliverpb.Envelope, error) {
t.Helper()
pollMsg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_POLL,
ID: sessionID,
Data: randomBytes(8),
}
pollDomain, err := encodeMessageDomain(example1, pollMsg)
if err != nil {
return nil, err
}
var respData []byte
if noTXT {
respData, _, err = resolver.AAAA(pollDomain)
} else {
respData, _, err = resolver.TXT(pollDomain)
}
if err != nil {
return nil, err
}
if len(respData) == 0 {
return nil, nil
}
manifest := &dnspb.DNSMessage{}
if err := proto.Unmarshal(respData, manifest); err != nil {
return nil, err
}
if manifest.Type != dnspb.DNSMessageType_MANIFEST || manifest.Size == 0 {
return nil, nil
}
bytesPerChunk := uint32(182)
if noTXT {
bytesPerChunk = 192
}
recvBuf := make([]byte, manifest.Size)
for start := uint32(0); start < manifest.Size; start += bytesPerChunk {
stop := start + bytesPerChunk
if manifest.Size < stop {
stop = manifest.Size
}
readMsg := &dnspb.DNSMessage{
ID: manifest.ID,
Type: dnspb.DNSMessageType_DATA_TO_IMPLANT,
Start: start,
Stop: stop,
}
domain, err := encodeMessageDomain(example1, readMsg)
if err != nil {
return nil, err
}
var readData []byte
if noTXT {
readData, _, err = resolver.AAAA(domain)
} else {
readData, _, err = resolver.TXT(domain)
}
if err != nil {
return nil, err
}
recvMsg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(readData, recvMsg); err != nil {
return nil, err
}
copy(recvBuf[recvMsg.Start:], recvMsg.Data)
}
plain, err := implantCrypto.NewCipherContext(key).Decrypt(recvBuf)
if err != nil && err != implantCrypto.ErrReplayAttack {
return nil, err
}
envelope := &sliverpb.Envelope{}
if err := proto.Unmarshal(plain, envelope); err != nil {
return nil, err
}
clearMsg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_CLEAR,
ID: manifest.ID,
Data: randomBytes(8),
}
clearDomain, err := encodeMessageDomain(example1, clearMsg)
if err != nil {
return nil, err
}
if _, _, err := resolver.A(clearDomain); err != nil {
return nil, err
}
return envelope, nil
}
func randomBytes(size int) []byte {
buf := make([]byte, size)
rand.Read(buf)
return buf
}
+1 -1
View File
@@ -31,13 +31,13 @@ import (
clientAssets "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/builder"
"github.com/bishopfox/sliver/server/c2"
"github.com/bishopfox/sliver/server/generate"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/version"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -21,7 +21,7 @@ package cli
import (
"fmt"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/server/version"
"github.com/spf13/cobra"
)
+22
View File
@@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"golang.org/x/mod/semver"
"gorm.io/gorm/callbacks"
_ "github.com/ncruces/go-sqlite3"
@@ -200,6 +201,27 @@ func (dialectopr Dialector) RollbackTo(tx *gorm.DB, name string) error {
}
func compareVersion(version1, version2 string) int {
semver1 := canonicalSQLiteVersion(version1)
semver2 := canonicalSQLiteVersion(version2)
if semver1 != "" && semver2 != "" {
return semver.Compare(semver1, semver2)
}
return compareVersionLegacy(version1, version2)
}
func canonicalSQLiteVersion(version string) string {
version = strings.TrimSpace(version)
if version == "" {
return ""
}
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
return semver.Canonical(version)
}
func compareVersionLegacy(version1, version2 string) int {
n, m := len(version1), len(version2)
i, j := 0, 0
for i < n || j < m {
+44 -12
View File
@@ -21,6 +21,8 @@ package generate
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -33,6 +35,23 @@ var (
nonce = 0
)
func cleanupGeneratedArtifacts(t *testing.T, outputPath string) {
t.Helper()
if outputPath == "" {
return
}
projectDir := filepath.Dir(filepath.Dir(outputPath))
sliversDir := GetSliversDir()
rel, err := filepath.Rel(sliversDir, projectDir)
if err != nil || rel == "." || strings.HasPrefix(rel, "..") {
t.Logf("cleanup skipped for unexpected path %q", projectDir)
return
}
if err := os.RemoveAll(projectDir); err != nil {
t.Logf("cleanup failed for %s: %v", projectDir, err)
}
}
func TestSliverExecutableWindows(t *testing.T) {
// Wireguard C2
@@ -228,10 +247,11 @@ func trafficEncodersExecutable(t *testing.T, goos string, goarch string) {
debugHttpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, debugConfig)
nonce++
_, err := SliverExecutable(name, build, debugConfig, debugHttpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, debugConfig, debugHttpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
name = fmt.Sprintf("trafficEncodersProd_test%d", nonce)
prodConfig := &clientpb.ImplantConfig{
GOOS: goos,
@@ -247,10 +267,11 @@ func trafficEncodersExecutable(t *testing.T, goos string, goarch string) {
}
build, _ = GenerateConfig(name, prodConfig)
nonce++
_, err = SliverExecutable(name, build, prodConfig, debugHttpC2Config.ImplantConfig)
binPath, err = SliverExecutable(name, build, prodConfig, debugHttpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func mtlsExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -270,10 +291,11 @@ func mtlsExe(t *testing.T, goos string, goarch string, beacon bool, debug bool)
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func dnsExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -293,10 +315,11 @@ func dnsExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func httpExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -320,10 +343,11 @@ func httpExe(t *testing.T, goos string, goarch string, beacon bool, debug bool)
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func multiExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -351,10 +375,11 @@ func multiExe(t *testing.T, goos string, goarch string, beacon bool, debug bool)
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func multiWindowsService(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -381,10 +406,11 @@ func multiWindowsService(t *testing.T, goos string, goarch string, beacon bool,
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
// Pivots do not support beacon mode
@@ -408,10 +434,11 @@ func tcpPivotExe(t *testing.T, goos string, goarch string, debug bool) {
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func namedPipeExe(t *testing.T, goos string, goarch string, debug bool) {
@@ -434,10 +461,11 @@ func namedPipeExe(t *testing.T, goos string, goarch string, debug bool) {
httpC2Config := configs.GenerateDefaultHTTPC2Config()
build, _ := GenerateConfig(name, config)
nonce++
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func wireguardExe(t *testing.T, goos string, goarch string, beacon bool, debug bool) {
@@ -465,10 +493,11 @@ func wireguardExe(t *testing.T, goos string, goarch string, beacon bool, debug b
nonce++
certs.SetupWGKeys()
build, _ := GenerateConfig(name, config)
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
@@ -502,10 +531,11 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
httpC2Config := configs.GenerateDefaultHTTPC2Config()
nonce++
build, _ := GenerateConfig(name, config)
_, err := SliverSharedLibrary(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverSharedLibrary(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
@@ -537,6 +567,7 @@ func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
if err != nil {
t.Fatalf("%v", err)
}
defer cleanupGeneratedArtifacts(t, binPath)
// encode bin with sgn
bin, err := os.ReadFile(binPath)
@@ -577,8 +608,9 @@ func symbolObfuscation(t *testing.T, goos string, goarch string) {
httpC2Config := configs.GenerateDefaultHTTPC2Config()
nonce++
build, _ := GenerateConfig(name, config)
_, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("%v", err)
}
cleanupGeneratedArtifacts(t, binPath)
}
+1 -1
View File
@@ -25,7 +25,6 @@ import (
"strings"
"time"
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -33,6 +32,7 @@ import (
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/version"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
"google.golang.org/protobuf/proto"
+172
View File
@@ -0,0 +1,172 @@
package version
/*
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 (
"errors"
"fmt"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
)
const defaultVersion = "devel"
var (
// Version - The semantic version in string form
Version string
// GoVersion - Go compiler version
GoVersion string
// GitCommit - The commit id at compile time
GitCommit string
// GitDirty - Was the commit dirty at compile time
GitDirty string
// CompiledAt - When was this binary compiled
CompiledAt string
)
func init() {
applyBuildInfo()
}
func applyBuildInfo() {
info, ok := debug.ReadBuildInfo()
if !ok || info == nil {
ensureDefaults()
return
}
if GoVersion == "" {
if info.GoVersion != "" {
GoVersion = info.GoVersion
} else {
GoVersion = runtime.Version()
}
}
if Version == "" {
Version = normalizeVersion(info.Main.Version)
}
settings := buildSettings(info.Settings)
if GitCommit == "" {
GitCommit = settings["vcs.revision"]
}
if GitDirty == "" && settings["vcs.modified"] == "true" {
GitDirty = "Dirty"
}
if CompiledAt == "" {
if vcsTime := settings["vcs.time"]; vcsTime != "" {
if compiled, err := time.Parse(time.RFC3339, vcsTime); err == nil {
CompiledAt = strconv.FormatInt(compiled.Unix(), 10)
}
}
}
ensureDefaults()
}
func ensureDefaults() {
if Version == "" {
Version = defaultVersion
}
if GoVersion == "" {
GoVersion = runtime.Version()
}
}
func buildSettings(settings []debug.BuildSetting) map[string]string {
values := make(map[string]string, len(settings))
for _, setting := range settings {
values[setting.Key] = setting.Value
}
return values
}
func normalizeVersion(version string) string {
if version == "" || version == "(devel)" {
return defaultVersion
}
if canonical := canonicalSemver(version); canonical != "" {
return canonical
}
return version
}
func canonicalSemver(version string) string {
version = strings.TrimSpace(version)
if version == "" || version == "(devel)" {
return ""
}
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
return semver.Canonical(version)
}
// SemanticVersion - Get the structured sematic version
func SemanticVersion() []int {
semVer := make([]int, 3)
canonical := canonicalSemver(Version)
if canonical == "" {
return semVer
}
base := strings.TrimPrefix(canonical, "v")
if idx := strings.Index(base, "-"); idx != -1 {
base = base[:idx]
}
parts := strings.Split(base, ".")
for i := 0; i < 3 && i < len(parts); i++ {
semVer[i], _ = strconv.Atoi(parts[i])
}
return semVer
}
// Compiled - Get time this binary was compiled
func Compiled() (time.Time, error) {
if CompiledAt == "" {
return time.Unix(0, 0), errors.New("compiled time not available")
}
compiled, err := strconv.ParseInt(CompiledAt, 10, 64)
if err != nil {
return time.Unix(0, 0), err
}
return time.Unix(compiled, 0), nil
}
// FullVersion - Full version string
func FullVersion() string {
ver := fmt.Sprintf("%s", Version)
compiled, err := Compiled()
if err == nil {
ver += fmt.Sprintf(" - Compiled %s", compiled.String())
}
if GitCommit != "" {
ver += fmt.Sprintf(" - %s", GitCommit)
}
return ver
}
+215
View File
@@ -0,0 +1,215 @@
package assets
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
func extractTarGz(archivePath, destDir string) error {
file, err := os.Open(archivePath)
if err != nil {
return fmt.Errorf("open tar.gz: %w", err)
}
defer file.Close()
gz, err := gzip.NewReader(file)
if err != nil {
return fmt.Errorf("open gzip stream: %w", err)
}
defer gz.Close()
tarReader := tar.NewReader(gz)
for {
hdr, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("read tar entry: %w", err)
}
target, err := safeJoin(destDir, hdr.Name)
if err != nil {
return err
}
mode := hdr.FileInfo().Mode()
switch hdr.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, mode.Perm()); err != nil {
return fmt.Errorf("create dir: %w", err)
}
case tar.TypeReg, tar.TypeRegA:
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return fmt.Errorf("create parent dir: %w", err)
}
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode.Perm())
if err != nil {
return fmt.Errorf("create file: %w", err)
}
if _, err := io.Copy(out, tarReader); err != nil {
out.Close()
return fmt.Errorf("write file: %w", err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("close file: %w", err)
}
case tar.TypeSymlink:
if filepath.IsAbs(hdr.Linkname) {
return fmt.Errorf("refusing absolute symlink: %s", hdr.Linkname)
}
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return fmt.Errorf("create parent dir: %w", err)
}
if err := os.Symlink(hdr.Linkname, target); err != nil {
return fmt.Errorf("create symlink: %w", err)
}
default:
// Skip non-file entries like extended headers.
continue
}
}
return nil
}
func extractZip(archivePath, destDir string) error {
reader, err := zip.OpenReader(archivePath)
if err != nil {
return fmt.Errorf("open zip: %w", err)
}
defer reader.Close()
for _, file := range reader.File {
target, err := safeJoin(destDir, file.Name)
if err != nil {
return err
}
info := file.FileInfo()
if info.IsDir() {
if err := os.MkdirAll(target, info.Mode().Perm()); err != nil {
return fmt.Errorf("create dir: %w", err)
}
continue
}
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return fmt.Errorf("create parent dir: %w", err)
}
in, err := file.Open()
if err != nil {
return fmt.Errorf("open zip entry: %w", err)
}
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode().Perm())
if err != nil {
in.Close()
return fmt.Errorf("create file: %w", err)
}
if _, err := io.Copy(out, in); err != nil {
in.Close()
out.Close()
return fmt.Errorf("write file: %w", err)
}
if err := in.Close(); err != nil {
out.Close()
return fmt.Errorf("close zip entry: %w", err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("close file: %w", err)
}
}
return nil
}
func zipDir(baseDir, relRoot, destZip string) error {
root := filepath.Join(baseDir, relRoot)
if err := ensureDir(filepath.Dir(destZip)); err != nil {
return err
}
zipFile, err := os.Create(destZip)
if err != nil {
return fmt.Errorf("create zip: %w", err)
}
defer zipFile.Close()
zw := zip.NewWriter(zipFile)
defer zw.Close()
return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
rel, err := filepath.Rel(baseDir, path)
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(rel)
if info.IsDir() {
if !strings.HasSuffix(header.Name, "/") {
header.Name += "/"
}
header.Method = zip.Store
_, err := zw.CreateHeader(header)
return err
}
header.Method = zip.Deflate
writer, err := zw.CreateHeader(header)
if err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
if _, err := io.Copy(writer, file); err != nil {
file.Close()
return err
}
if err := file.Close(); err != nil {
return err
}
return nil
})
}
func safeJoin(root, name string) (string, error) {
cleaned := filepath.Clean(name)
if filepath.IsAbs(cleaned) {
return "", fmt.Errorf("absolute path in archive: %s", name)
}
target := filepath.Join(root, cleaned)
rel, err := filepath.Rel(root, target)
if err != nil {
return "", fmt.Errorf("resolve archive path: %w", err)
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return "", fmt.Errorf("archive path escapes destination: %s", name)
}
return target, nil
}
+148
View File
@@ -0,0 +1,148 @@
package assets
import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
type runner struct {
logger *logger
httpClient *http.Client
outputDir string
workDir string
goIndex int
zigIndex int
garbleIndex int
zigMirrors []string
}
type config struct {
verbose bool
quiet bool
noColor bool
}
// Run executes the asset generation flow.
func Run(args []string) error {
cfg, showHelp, err := parseArgs(args)
if err != nil {
return err
}
if showHelp {
fmt.Println(usage())
return nil
}
repoRoot, err := findRepoRoot()
if err != nil {
return err
}
outputDir := filepath.Join(repoRoot, "server", "assets", "fs")
if err := os.MkdirAll(outputDir, 0o755); err != nil {
return fmt.Errorf("create output dir: %w", err)
}
workDir, err := os.MkdirTemp("", "sliver-assets-")
if err != nil {
return fmt.Errorf("create work dir: %w", err)
}
log := newLogger(cfg.verbose, cfg.quiet, cfg.noColor)
r := &runner{
logger: log,
httpClient: &http.Client{Timeout: 15 * time.Minute},
outputDir: outputDir,
workDir: workDir,
}
log.Header("Sliver Assets")
log.Meta("Workdir", workDir)
log.Meta("Output", outputDir)
defer func() {
log.ClearSection()
log.Logf("clean up: %s", workDir)
os.RemoveAll(workDir)
}()
if err := r.buildGoAssets(); err != nil {
return err
}
if err := r.buildZigAssets(); err != nil {
return err
}
if err := r.buildGarbleAssets(); err != nil {
return err
}
log.ClearSection()
log.Logf("")
log.Successf("Done")
return nil
}
func parseArgs(args []string) (config, bool, error) {
cfg := config{}
showHelp := false
for _, arg := range args {
switch arg {
case "-v", "--verbose":
cfg.verbose = true
case "--no-colors":
cfg.noColor = true
case "--quiet":
cfg.quiet = true
case "-h", "--help":
showHelp = true
default:
return config{}, false, fmt.Errorf("unknown argument: %s", arg)
}
}
if cfg.quiet {
cfg.verbose = false
}
return cfg, showHelp, nil
}
func usage() string {
return "Usage: assets [--verbose] [--quiet] [--no-colors]"
}
func findRepoRoot() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("get working directory: %w", err)
}
dir := cwd
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", errors.New("unable to locate go.mod from current directory")
}
dir = parent
}
}
func ensureDir(path string) error {
if path == "" {
return errors.New("empty directory path")
}
return os.MkdirAll(path, 0o755)
}
func trimLeadingDot(path string) string {
return strings.TrimPrefix(path, "./")
}
+40
View File
@@ -0,0 +1,40 @@
package assets
const (
goVersion = "1.25.5"
zigVersion = "0.15.1"
garbleVersion = "1.25.5"
zigSourceParam = "source=sliver"
zigMinisignPublicKey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U"
goTotal = 5
zigTotal = 5
garbleTotal = 5
)
var defaultZigMirrors = []string{
"https://pkg.machengine.org/zig",
"https://zigmirror.hryx.net/zig",
"https://zig.linus.dev/zig",
"https://zig.squirl.dev",
"https://zig.florent.dev",
"https://zig.mirror.mschae23.de/zig",
"https://zigmirror.meox.dev",
}
var goBloatPaths = []string{
"AUTHORS",
"CONTRIBUTORS",
"PATENTS",
"VERSION",
"favicon.ico",
"robots.txt",
"SECURITY.md",
"CONTRIBUTING.md",
"README.md",
"doc",
"test",
"api",
"misc",
}
+140
View File
@@ -0,0 +1,140 @@
package assets
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
)
func (r *runner) downloadFile(url, dest string) error {
if err := ensureDir(filepath.Dir(dest)); err != nil {
return err
}
return r.downloadFileTo(url, dest)
}
func (r *runner) downloadFileTo(url, dest string) error {
tmpFile, err := os.CreateTemp(filepath.Dir(dest), ".download-*")
if err != nil {
return fmt.Errorf("create temp file: %w", err)
}
tmpName := tmpFile.Name()
defer func() {
tmpFile.Close()
os.Remove(tmpName)
}()
if err := r.logger.RunSpinner(fmt.Sprintf("fetch %s", downloadLabel(url)), func() error {
return r.fetchToWriter(url, tmpFile)
}); err != nil {
return err
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("flush download: %w", err)
}
if err := moveFile(tmpName, dest); err != nil {
return fmt.Errorf("move download into place: %w", err)
}
return nil
}
func (r *runner) downloadToTemp(url, dir string) (string, error) {
if err := ensureDir(dir); err != nil {
return "", err
}
file, err := os.CreateTemp(dir, "download-*")
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
name := file.Name()
if err := r.logger.RunSpinner(fmt.Sprintf("fetch %s", downloadLabel(url)), func() error {
return r.fetchToWriter(url, file)
}); err != nil {
file.Close()
os.Remove(name)
return "", err
}
if err := file.Close(); err != nil {
os.Remove(name)
return "", fmt.Errorf("flush download: %w", err)
}
return name, nil
}
func (r *runner) fetchToWriter(url string, w io.Writer) error {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("build request: %w", err)
}
req.Header.Set("User-Agent", "sliver-assets")
resp, err := r.httpClient.Do(req)
if err != nil {
return fmt.Errorf("download %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return fmt.Errorf("download %s: unexpected status %s", url, resp.Status)
}
if _, err := io.Copy(w, resp.Body); err != nil {
return fmt.Errorf("stream %s: %w", url, err)
}
return nil
}
func moveFile(src, dest string) error {
if err := os.Rename(src, dest); err == nil {
return nil
}
if err := copyFile(src, dest); err != nil {
return err
}
return os.Remove(src)
}
func copyFile(src, dest string) error {
in, err := os.Open(src)
if err != nil {
return fmt.Errorf("open source: %w", err)
}
defer in.Close()
if err := ensureDir(filepath.Dir(dest)); err != nil {
return err
}
out, err := os.Create(dest)
if err != nil {
return fmt.Errorf("create dest: %w", err)
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return fmt.Errorf("copy file: %w", err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("flush dest: %w", err)
}
return nil
}
func downloadLabel(rawURL string) string {
parsed, err := url.Parse(rawURL)
if err != nil {
return rawURL
}
base := path.Base(parsed.Path)
if base == "." || base == "/" || base == "" {
return rawURL
}
return base
}
+65
View File
@@ -0,0 +1,65 @@
package assets
import (
"fmt"
"path/filepath"
)
type garblePlatform struct {
os string
arch string
filename string
url string
}
func (r *runner) buildGarbleAssets() error {
r.logger.Section("Garble")
platforms := []garblePlatform{
{
os: "linux",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-amd64", garbleVersion),
},
{
os: "linux",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-arm64", garbleVersion),
},
{
os: "windows",
arch: "amd64",
filename: "garble.exe",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_windows-amd64.exe", garbleVersion),
},
{
os: "darwin",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-amd64", garbleVersion),
},
{
os: "darwin",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-arm64", garbleVersion),
},
}
for _, platform := range platforms {
r.garbleIndex++
r.logger.Logf("Fetch garble %s/%s (%d/%d)", platform.os, platform.arch, r.garbleIndex, garbleTotal)
outputDir := filepath.Join(r.outputDir, platform.os, platform.arch)
if err := ensureDir(outputDir); err != nil {
return err
}
destPath := filepath.Join(outputDir, platform.filename)
if err := r.downloadFile(platform.url, destPath); err != nil {
return err
}
}
return nil
}
+157
View File
@@ -0,0 +1,157 @@
package assets
import (
"fmt"
"os"
"path/filepath"
)
type goPlatform struct {
os string
arch string
archiveExt string
includeSrc bool
toolRemoves []string
}
func (r *runner) buildGoAssets() error {
r.logger.Section("Go")
platforms := []goPlatform{
{
os: "darwin",
arch: "amd64",
archiveExt: "tar.gz",
includeSrc: true,
toolRemoves: []string{
"pkg/tool/darwin_amd64/doc",
"pkg/tool/darwin_amd64/tour",
"pkg/tool/darwin_amd64/test2json",
},
},
{
os: "darwin",
arch: "arm64",
archiveExt: "tar.gz",
includeSrc: true,
toolRemoves: []string{
"pkg/tool/darwin_arm64/doc",
"pkg/tool/darwin_arm64/tour",
"pkg/tool/darwin_arm64/test2json",
},
},
{
os: "linux",
arch: "amd64",
archiveExt: "tar.gz",
toolRemoves: []string{
"pkg/tool/linux_amd64/doc",
"pkg/tool/linux_amd64/tour",
"pkg/tool/linux_amd64/test2json",
},
},
{
os: "linux",
arch: "arm64",
archiveExt: "tar.gz",
toolRemoves: []string{
"pkg/tool/linux_arm64/doc",
"pkg/tool/linux_arm64/tour",
"pkg/tool/linux_arm64/test2json",
},
},
{
os: "windows",
arch: "amd64",
archiveExt: "zip",
toolRemoves: []string{
"pkg/tool/windows_amd64/doc.exe",
"pkg/tool/windows_amd64/tour.exe",
"pkg/tool/windows_amd64/test2json.exe",
},
},
}
for _, platform := range platforms {
label := fmt.Sprintf("%s/%s", platform.os, platform.arch)
r.goIndex++
r.logger.Logf("Fetch go %s (%d/%d)", label, r.goIndex, goTotal)
archiveName := fmt.Sprintf("go%s.%s-%s.%s", goVersion, platform.os, platform.arch, platform.archiveExt)
archiveURL := fmt.Sprintf("https://dl.google.com/go/%s", archiveName)
archivePath := filepath.Join(r.workDir, archiveName)
if err := r.downloadFile(archiveURL, archivePath); err != nil {
return err
}
r.logger.Logf("Extract go %s", label)
goDir := filepath.Join(r.workDir, "go")
if err := os.RemoveAll(goDir); err != nil {
return fmt.Errorf("remove previous go dir: %w", err)
}
if platform.archiveExt == "zip" {
if err := extractZip(archivePath, r.workDir); err != nil {
return err
}
} else {
if err := extractTarGz(archivePath, r.workDir); err != nil {
return err
}
}
if err := removePaths(goDir, goBloatPaths); err != nil {
return err
}
if platform.includeSrc {
r.logger.Logf("Pack src.zip (%s)", label)
srcZipPath := filepath.Join(r.outputDir, "src.zip")
if err := zipDir(goDir, "src", srcZipPath); err != nil {
return err
}
}
if err := os.RemoveAll(filepath.Join(goDir, "src")); err != nil {
return fmt.Errorf("remove src dir: %w", err)
}
if err := removePaths(goDir, platform.toolRemoves); err != nil {
return err
}
r.logger.Logf("Pack go.zip (%s)", label)
outputDir := filepath.Join(r.outputDir, platform.os, platform.arch)
if err := ensureDir(outputDir); err != nil {
return err
}
destZip := filepath.Join(outputDir, "go.zip")
if err := zipDir(r.workDir, "go", destZip); err != nil {
return err
}
if err := os.RemoveAll(goDir); err != nil {
return fmt.Errorf("remove go dir: %w", err)
}
if err := os.Remove(archivePath); err != nil {
return fmt.Errorf("remove go archive: %w", err)
}
}
return nil
}
func removePaths(root string, paths []string) error {
for _, path := range paths {
path = trimLeadingDot(path)
if path == "" {
continue
}
full := filepath.Join(root, path)
if err := os.RemoveAll(full); err != nil {
return fmt.Errorf("remove %s: %w", full, err)
}
}
return nil
}
+190
View File
@@ -0,0 +1,190 @@
package assets
import (
"context"
"fmt"
"io"
"os"
"strings"
huhspinner "github.com/charmbracelet/huh/spinner"
"github.com/charmbracelet/lipgloss"
"golang.org/x/term"
)
type styles struct {
title lipgloss.Style
section lipgloss.Style
tag lipgloss.Style
key lipgloss.Style
success lipgloss.Style
warn lipgloss.Style
error lipgloss.Style
muted lipgloss.Style
spinner lipgloss.Style
}
type logger struct {
verbose bool
quiet bool
out io.Writer
err io.Writer
styles styles
spinnerEnabled bool
section string
sectionCount int
}
func newLogger(verbose, quiet, noColor bool) *logger {
out := os.Stdout
err := os.Stderr
var styled styles
if noColor {
styled = styles{
title: lipgloss.NewStyle(),
section: lipgloss.NewStyle(),
tag: lipgloss.NewStyle(),
key: lipgloss.NewStyle(),
success: lipgloss.NewStyle(),
warn: lipgloss.NewStyle(),
error: lipgloss.NewStyle(),
muted: lipgloss.NewStyle(),
spinner: lipgloss.NewStyle(),
}
} else {
styled = styles{
title: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("81")),
section: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("75")),
tag: lipgloss.NewStyle().Foreground(lipgloss.Color("69")),
key: lipgloss.NewStyle().Foreground(lipgloss.Color("242")),
success: lipgloss.NewStyle().Foreground(lipgloss.Color("82")),
warn: lipgloss.NewStyle().Foreground(lipgloss.Color("214")),
error: lipgloss.NewStyle().Foreground(lipgloss.Color("203")),
muted: lipgloss.NewStyle().Foreground(lipgloss.Color("242")),
spinner: lipgloss.NewStyle().Foreground(lipgloss.Color("69")),
}
}
return &logger{
verbose: verbose,
quiet: quiet,
out: out,
err: err,
styles: styled,
spinnerEnabled: !verbose && !quiet && isTerminal(out),
}
}
func (l *logger) Header(title string) {
if l.quiet {
return
}
fmt.Fprintln(l.out, l.styles.title.Render(title))
}
func (l *logger) Meta(key, value string) {
if l.quiet {
return
}
label := l.styles.key.Render(fmt.Sprintf("%s:", strings.ToLower(key)))
fmt.Fprintf(l.out, "%s %s\n", label, value)
}
func (l *logger) Section(title string) {
l.section = strings.ToLower(title)
if l.quiet {
return
}
if l.sectionCount > 0 {
fmt.Fprintln(l.out)
}
l.sectionCount++
fmt.Fprintln(l.out, l.styles.section.Render(title))
}
func (l *logger) ClearSection() {
l.section = ""
}
func (l *logger) Logf(format string, args ...any) {
if l.quiet {
return
}
msg := fmt.Sprintf(format, args...)
if msg == "" {
fmt.Fprintln(l.out)
return
}
fmt.Fprintln(l.out, l.withPrefix(msg))
}
func (l *logger) VLogf(format string, args ...any) {
if l.quiet || !l.verbose {
return
}
msg := fmt.Sprintf(format, args...)
fmt.Fprintln(l.out, l.withPrefix(l.styles.muted.Render(msg)))
}
func (l *logger) Successf(format string, args ...any) {
if l.quiet {
return
}
msg := fmt.Sprintf(format, args...)
fmt.Fprintln(l.out, l.withPrefix(l.styles.success.Render(msg)))
}
func (l *logger) Warnf(format string, args ...any) {
if l.quiet {
return
}
msg := fmt.Sprintf(format, args...)
fmt.Fprintln(l.out, l.withPrefix(l.styles.warn.Render(msg)))
}
func (l *logger) Errorf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintln(l.err, l.withPrefix(l.styles.error.Render(msg)))
}
func (l *logger) RunSpinner(label string, action func() error) error {
if l.quiet || !l.spinnerEnabled {
return action()
}
spin := huhspinner.New().
Type(huhspinner.Pulse).
Title(label).
TitleStyle(l.styles.muted).
Style(l.styles.spinner).
Output(l.out).
ActionWithErr(func(context.Context) error {
return action()
})
return spin.Run()
}
func (l *logger) withPrefix(msg string) string {
prefix := l.prefix()
if prefix == "" {
return msg
}
return prefix + " " + msg
}
func (l *logger) prefix() string {
if l.section == "" {
return ""
}
return l.styles.tag.Render("[" + l.section + "]")
}
func isTerminal(w io.Writer) bool {
file, ok := w.(*os.File)
if !ok {
return false
}
return term.IsTerminal(int(file.Fd()))
}
+244
View File
@@ -0,0 +1,244 @@
package assets
import (
"bufio"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
insecureRand "math/rand"
minisign "github.com/bishopfox/sliver/util/minisign"
)
type zigPlatform struct {
os string
arch string
remoteName string
localName string
}
func (r *runner) buildZigAssets() error {
r.logger.Section("Zig")
if err := r.loadZigMirrors(); err != nil {
return err
}
platforms := []zigPlatform{
{os: "darwin", arch: "amd64", remoteName: fmt.Sprintf("zig-x86_64-macos-%s.tar.xz", zigVersion), localName: "zig.tar.xz"},
{os: "darwin", arch: "arm64", remoteName: fmt.Sprintf("zig-aarch64-macos-%s.tar.xz", zigVersion), localName: "zig.tar.xz"},
{os: "linux", arch: "amd64", remoteName: fmt.Sprintf("zig-x86_64-linux-%s.tar.xz", zigVersion), localName: "zig.tar.xz"},
{os: "linux", arch: "arm64", remoteName: fmt.Sprintf("zig-aarch64-linux-%s.tar.xz", zigVersion), localName: "zig.tar.xz"},
{os: "windows", arch: "amd64", remoteName: fmt.Sprintf("zig-x86_64-windows-%s.zip", zigVersion), localName: "zig.zip"},
}
for _, platform := range platforms {
if err := r.downloadZig(platform); err != nil {
return err
}
}
return nil
}
func (r *runner) loadZigMirrors() error {
if len(r.zigMirrors) > 0 {
return nil
}
client := *r.httpClient
client.Timeout = 30 * time.Second
resp, err := client.Get("https://ziglang.org/download/community-mirrors.txt")
if err == nil && resp != nil {
defer resp.Body.Close()
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
mirrors, err := parseZigMirrors(resp.Body)
if err == nil && len(mirrors) > 0 {
r.zigMirrors = mirrors
return nil
}
}
}
r.zigMirrors = append([]string{}, defaultZigMirrors...)
return nil
}
func parseZigMirrors(reader io.Reader) ([]string, error) {
scanner := bufio.NewScanner(reader)
mirrors := []string{}
for scanner.Scan() {
line := scanner.Text()
if idx := strings.IndexRune(line, '#'); idx >= 0 {
line = line[:idx]
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
mirrors = append(mirrors, line)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return mirrors, nil
}
func (r *runner) downloadZig(platform zigPlatform) error {
outputDir := filepath.Join(r.outputDir, platform.os, platform.arch)
if err := ensureDir(outputDir); err != nil {
return err
}
destPath := filepath.Join(outputDir, platform.localName)
if err := os.RemoveAll(destPath); err != nil {
return fmt.Errorf("remove existing zig file: %w", err)
}
r.zigIndex++
mirrors := r.randomizedMirrors()
for idx, mirror := range mirrors {
mirrorBase := strings.TrimRight(mirror, "/") + "/" + zigVersion
artifactURL := appendQueryParam(mirrorBase+"/"+platform.remoteName, zigSourceParam)
signatureURL := appendQueryParam(mirrorBase+"/"+platform.remoteName+".minisig", zigSourceParam)
r.logger.Logf("Fetch zig %s/%s (%d/%d) via %s", platform.os, platform.arch, r.zigIndex, zigTotal, mirrorBase)
r.logger.VLogf(" artifact: %s", artifactURL)
r.logger.VLogf(" signature: %s", signatureURL)
artifactPath, err := r.downloadToTemp(artifactURL, r.workDir)
if err != nil {
r.logger.Errorf("Failed to download Zig artifact from %s", artifactURL)
if idx < len(mirrors)-1 {
r.logger.Warnf("Trying alternate mirror")
}
continue
}
signaturePath, err := r.downloadToTemp(signatureURL, r.workDir)
if err != nil {
os.Remove(artifactPath)
r.logger.Errorf("Failed to download Zig signature from %s", signatureURL)
if idx < len(mirrors)-1 {
r.logger.Warnf("Trying alternate mirror")
}
continue
}
if err := verifyZigSignature(artifactPath, signaturePath, platform.remoteName); err != nil {
r.logger.Errorf("Signature verification failed for %s from %s", platform.remoteName, mirrorBase)
r.logger.Errorf("Deleting corrupted download %s", artifactPath)
os.Remove(artifactPath)
os.Remove(signaturePath)
if idx < len(mirrors)-1 {
r.logger.Warnf("Trying alternate mirror")
}
continue
}
if err := moveFile(artifactPath, destPath); err != nil {
os.Remove(artifactPath)
os.Remove(signaturePath)
return err
}
os.Remove(signaturePath)
r.logger.Successf("Verified zig package -> %s", destPath)
return nil
}
return fmt.Errorf("unable to download and verify Zig package %s", platform.remoteName)
}
func (r *runner) randomizedMirrors() []string {
mirrors := append([]string{}, r.zigMirrors...)
if len(mirrors) == 0 {
return mirrors
}
insecureRand.Shuffle(len(mirrors), func(i, j int) {
mirrors[i], mirrors[j] = mirrors[j], mirrors[i]
})
return mirrors
}
func appendQueryParam(url, param string) string {
if strings.Contains(url, "?") {
return url + "&" + param
}
return url + "?" + param
}
func verifyZigSignature(artifactPath, signaturePath, expectedFile string) error {
sigData, err := os.ReadFile(signaturePath)
if err != nil {
return fmt.Errorf("read signature: %w", err)
}
pubKey, err := loadZigPublicKey()
if err != nil {
return err
}
artifact, err := os.Open(artifactPath)
if err != nil {
return fmt.Errorf("open artifact: %w", err)
}
defer artifact.Close()
reader := minisign.NewReader(artifact)
if _, err := io.Copy(io.Discard, reader); err != nil {
return fmt.Errorf("stream artifact: %w", err)
}
if !reader.Verify(pubKey, sigData) {
return errors.New("signature verification failed")
}
if err := verifyTrustedComment(sigData, expectedFile); err != nil {
return err
}
return nil
}
func loadZigPublicKey() (minisign.PublicKey, error) {
key := os.Getenv("ZIG_PUBLIC_KEY")
if key == "" {
key = zigMinisignPublicKey
}
var publicKey minisign.PublicKey
if err := publicKey.UnmarshalText([]byte(key)); err != nil {
return minisign.PublicKey{}, fmt.Errorf("invalid minisign public key: %w", err)
}
return publicKey, nil
}
func verifyTrustedComment(signature []byte, expectedFile string) error {
var sig minisign.Signature
if err := sig.UnmarshalText(signature); err != nil {
return fmt.Errorf("parse signature: %w", err)
}
fileField := trustedCommentFile(sig.TrustedComment)
if fileField == "" {
return fmt.Errorf("trusted comment missing file field: %q", sig.TrustedComment)
}
if fileField != expectedFile {
return fmt.Errorf("trusted comment file mismatch: expected %q, got %q", expectedFile, fileField)
}
return nil
}
func trustedCommentFile(trustedComment string) string {
for _, field := range strings.Fields(trustedComment) {
if after, ok := strings.CutPrefix(field, "file:"); ok {
return after
}
}
return ""
}
+15
View File
@@ -0,0 +1,15 @@
package main
import (
"fmt"
"os"
"github.com/bishopfox/sliver/util/assets"
)
func main() {
if err := assets.Run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Loaded 100 of 1846 files, more files were not shown because too many files have changed in this diff. Show more