mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
34
Commits
polite-zig
...
fix/forms
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8d59daded | ||
|
|
9195376993 | ||
|
|
a6de8e0304 | ||
|
|
310619526d | ||
|
|
1990777c4b | ||
|
|
9570556a87 | ||
|
|
b563919c19 | ||
|
|
f1858ee8f3 | ||
|
|
46a8313a33 | ||
|
|
868b2c294c | ||
|
|
9827b4df4b | ||
|
|
a48309a813 | ||
|
|
5df5dec5d4 | ||
|
|
3ff3ef2ed4 | ||
|
|
73cc8c38ff | ||
|
|
ec7e2facbc | ||
|
|
4969c2dc51 | ||
|
|
94cb60b1f7 | ||
|
|
33fcfd54ef | ||
|
|
10bb248e50 | ||
|
|
e023fd507e | ||
|
|
1a9f0f38dd | ||
|
|
02831a80bf | ||
|
|
9ccff1798e | ||
|
|
cd5ef8b5c6 | ||
|
|
dda1fdbbc2 | ||
|
|
3b49df5662 | ||
|
|
32be71ff8e | ||
|
|
3f77da1524 | ||
|
|
bae3e9e45d | ||
|
|
fae7e211a7 | ||
|
|
346da14431 | ||
|
|
15174f6c31 | ||
|
|
f29df040bb |
No files matched your search
@@ -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_*
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
## Testing Guidelines
|
||||
- Go tests follow standard `_test.go` naming within each package.
|
||||
- Prefer `./go-tests.sh` (uses tags like `client`, `server`, `osusergo`, `netgo`, `go_sqlite`) to match CI expectations.
|
||||
- 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
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 🤔")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
Generated
+68
-10
@@ -22,7 +22,7 @@
|
||||
"framer-motion": "^12.23.22",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lunr": "^2.3.9",
|
||||
"next": "^15.5.7",
|
||||
"next": "^15.5.9",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
@@ -71,6 +71,7 @@
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -92,6 +93,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
||||
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -2499,6 +2501,7 @@
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -2508,9 +2511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
|
||||
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
|
||||
"version": "15.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
|
||||
"integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
@@ -4330,6 +4333,60 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.6.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.6.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.0.7",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
|
||||
@@ -4445,6 +4502,7 @@
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -6481,6 +6539,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -9430,12 +9489,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.7",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
|
||||
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
|
||||
"version": "15.5.9",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
|
||||
"integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.7",
|
||||
"@next/env": "15.5.9",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
@@ -11049,8 +11108,7 @@
|
||||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
|
||||
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"framer-motion": "^12.23.22",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lunr": "^2.3.9",
|
||||
"next": "^15.5.7",
|
||||
"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`
|
||||
-332
@@ -1,332 +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.5"
|
||||
GARBLE_VER="1.25.5"
|
||||
ZIG_VER="0.15.1"
|
||||
ZIG_SOURCE_PARAM="source=sliver"
|
||||
|
||||
# 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[@]}"
|
||||
}
|
||||
|
||||
append_query_param() {
|
||||
local url="$1"
|
||||
local param="$2"
|
||||
if [[ "$url" == *\?* ]]; then
|
||||
printf '%s&%s' "$url" "$param"
|
||||
else
|
||||
printf '%s?%s' "$url" "$param"
|
||||
fi
|
||||
}
|
||||
|
||||
# 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" "$3"
|
||||
}
|
||||
|
||||
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 success=0
|
||||
|
||||
mkdir -p "$dest_dir"
|
||||
rm -f "$dest_path"
|
||||
|
||||
echo "Zig $platform/$arch: $remote_name"
|
||||
while read -r mirror; do
|
||||
local mirror_base="${mirror%/}/$ZIG_VER"
|
||||
local artifact_url="${mirror_base}/$remote_name"
|
||||
local signature_url="${mirror_base}/${remote_name}.minisig"
|
||||
artifact_url="$(append_query_param "$artifact_url" "$ZIG_SOURCE_PARAM")"
|
||||
signature_url="$(append_query_param "$signature_url" "$ZIG_SOURCE_PARAM")"
|
||||
echo "Attempting Zig mirror $mirror_base"
|
||||
echo " artifact: $artifact_url"
|
||||
echo " signature: $signature_url"
|
||||
local tmp_tar
|
||||
local tmp_sig
|
||||
tmp_tar=$(mktemp)
|
||||
tmp_sig=$(mktemp)
|
||||
local verification_failed=0
|
||||
if curl -L --fail --output "$tmp_tar" "$artifact_url"; then
|
||||
if curl -L --fail --output "$tmp_sig" "$signature_url"; then
|
||||
if verify_minisig "$tmp_tar" "$tmp_sig" "$remote_name"; 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 $remote_name from $mirror_base" >&2
|
||||
verification_failed=1
|
||||
fi
|
||||
else
|
||||
echo "[!] Failed to download Zig signature from $signature_url" >&2
|
||||
fi
|
||||
else
|
||||
echo "[!] Failed to download Zig artifact from $artifact_url" >&2
|
||||
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_base" >&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
@@ -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
|
||||
@@ -6,13 +6,15 @@ replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v
|
||||
|
||||
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,17 +48,17 @@ 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
|
||||
@@ -75,13 +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/service/ssm v1.65.1 // 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
|
||||
@@ -91,6 +103,7 @@ require (
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // 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
|
||||
@@ -118,15 +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/netlink v1.8.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
|
||||
@@ -145,13 +163,13 @@ require (
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // 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/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.17.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
|
||||
|
||||
@@ -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=
|
||||
@@ -422,11 +459,12 @@ 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=
|
||||
@@ -448,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=
|
||||
@@ -468,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=
|
||||
@@ -479,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=
|
||||
@@ -492,43 +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-20200223170610-d5e6a3e2c0ae/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.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=
|
||||
@@ -540,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=
|
||||
@@ -573,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=
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/server/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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, "./")
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -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 ""
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// Command zigverify validates a Zig archive against a minisign signature.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
minisign "github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
const defaultZigPublicKey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U"
|
||||
|
||||
func loadPublicKey() (minisign.PublicKey, error) {
|
||||
key := os.Getenv("ZIG_PUBLIC_KEY")
|
||||
if key == "" {
|
||||
key = defaultZigPublicKey
|
||||
}
|
||||
|
||||
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 verifySignature(artifactPath, signaturePath, expectedFile string) error {
|
||||
artifact, err := os.Open(artifactPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open artifact: %w", err)
|
||||
}
|
||||
defer artifact.Close()
|
||||
|
||||
sigData, err := os.ReadFile(signaturePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read signature: %w", err)
|
||||
}
|
||||
|
||||
pubKey, err := loadPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 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.FieldsSeq(trustedComment) {
|
||||
if after, ok := strings.CutPrefix(field, "file:"); ok {
|
||||
return after
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 4 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s <artifact-path> <signature-path> <expected-filename>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := verifySignature(os.Args[1], os.Args[2], os.Args[3]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
# Contributing to Survey
|
||||
|
||||
🎉🎉 First off, thanks for the interest in contributing to `survey`! 🎉🎉
|
||||
|
||||
The following is a set of guidelines to follow when contributing to this package. These are not hard rules, please use common sense and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and its contibutors are expected to uphold the [Go Community Code of Conduct](https://golang.org/conduct). By participating, you are expected to follow these guidelines.
|
||||
|
||||
## Getting help
|
||||
|
||||
* [Open an issue](https://github.com/AlecAivazis/survey/issues/new/choose)
|
||||
* Reach out to `@AlecAivazis` or `@mislav` in the Gophers slack (please use only when urgent)
|
||||
|
||||
## Submitting a contribution
|
||||
|
||||
When submitting a contribution,
|
||||
|
||||
- Try to make a series of smaller changes instead of one large change
|
||||
- Provide a description of each change that you are proposing
|
||||
- Reference the issue addressed by your pull request (if there is one)
|
||||
- Document all new exported Go APIs
|
||||
- Update the project's README when applicable
|
||||
- Include unit tests if possible
|
||||
- Contributions with visual ramifications or interaction changes should be accompanied with an integration test—see below for details.
|
||||
|
||||
## Writing and running tests
|
||||
|
||||
When submitting features, please add as many units tests as necessary to test both positive and negative cases.
|
||||
|
||||
Integration tests for survey uses [go-expect](https://github.com/Netflix/go-expect) to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, you need a way to interpret terminal / ANSI escape sequences for things like `CursorLocation`. The stdin/stdout handled by `go-expect` is also multiplexed to a [virtual terminal](https://github.com/hinshun/vt10x).
|
||||
|
||||
For example, you can extend the tests for Input by specifying the following test case:
|
||||
|
||||
```go
|
||||
{
|
||||
"Test Input prompt interaction", // Name of the test.
|
||||
&Input{ // An implementation of the survey.Prompt interface.
|
||||
Message: "What is your name?",
|
||||
},
|
||||
func(c *expect.Console) { // An expect procedure. You can expect strings / regexps and
|
||||
c.ExpectString("What is your name?") // write back strings / bytes to its psuedoterminal for survey.
|
||||
c.SendLine("Johnny Appleseed")
|
||||
c.ExpectEOF() // Nothing is read from the tty without an expect, and once an
|
||||
// expectation is met, no further bytes are read. End your
|
||||
// procedure with `c.ExpectEOF()` to read until survey finishes.
|
||||
},
|
||||
"Johnny Appleseed", // The expected result.
|
||||
}
|
||||
```
|
||||
|
||||
If you want to write your own `go-expect` test from scratch, you'll need to instantiate a virtual terminal,
|
||||
multiplex it into an `*expect.Console`, and hook up its tty with survey's optional stdio. Please see `go-expect`
|
||||
[documentation](https://godoc.org/github.com/Netflix/go-expect) for more detail.
|
||||
Loaded 100 of 553 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user