mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
369
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
eab9d0b653 | ||
|
|
29c4ec0afa | ||
|
|
b7c336cb76 | ||
|
|
d8ddd09c86 | ||
|
|
04bb356207 | ||
|
|
a003a5e3cf | ||
|
|
75e3cad76a | ||
|
|
8aa0cbee7e | ||
|
|
30a50717e1 | ||
|
|
f4c9e7109b | ||
|
|
08f3997756 | ||
|
|
3aebebecbb | ||
|
|
1d7eca8e39 | ||
|
|
773ee5f867 | ||
|
|
7608a4b0a0 | ||
|
|
9ecd36b7b6 | ||
|
|
d8fc5a6df4 | ||
|
|
649a6582e0 | ||
|
|
00e8a0cfca | ||
|
|
e9890bc8f9 | ||
|
|
8b798f8307 | ||
|
|
d4728af1ea | ||
|
|
d1b7d91b1a | ||
|
|
cac6dced34 | ||
|
|
a35bf0c1fc | ||
|
|
67616e50c1 | ||
|
|
54de8a2a97 | ||
|
|
20916c655c | ||
|
|
6c18201ed6 | ||
|
|
29165aff0d | ||
|
|
704285f28d | ||
|
|
a007872fbc | ||
|
|
c90e674f25 | ||
|
|
679b8d0c51 | ||
|
|
0a42a03052 | ||
|
|
73852e5bf2 | ||
|
|
5bcf59e1b3 | ||
|
|
935c737c35 | ||
|
|
5c04e61235 | ||
|
|
cff7a8ce53 | ||
|
|
e6f452ea2e | ||
|
|
c5f533bc80 | ||
|
|
bc08760320 | ||
|
|
9b37bee391 | ||
|
|
709085238d | ||
|
|
6e4240f0d9 | ||
|
|
c95e5c6cd7 | ||
|
|
ed3ab31741 | ||
|
|
9372b768be | ||
|
|
2d7406f415 | ||
|
|
85ba0cb587 | ||
|
|
251871cca1 | ||
|
|
604318ea54 | ||
|
|
bc72b20f9b | ||
|
|
61022e1eb1 | ||
|
|
a8d59daded | ||
|
|
9195376993 | ||
|
|
a6de8e0304 | ||
|
|
d3ebd34b0d | ||
|
|
310619526d | ||
|
|
1990777c4b | ||
|
|
9570556a87 | ||
|
|
b563919c19 | ||
|
|
f1858ee8f3 | ||
|
|
46a8313a33 | ||
|
|
22454f3385 | ||
|
|
868b2c294c | ||
|
|
9827b4df4b | ||
|
|
a48309a813 | ||
|
|
5df5dec5d4 | ||
|
|
a0fb2a229f | ||
|
|
3ff3ef2ed4 | ||
|
|
73cc8c38ff | ||
|
|
896c763166 | ||
|
|
ec7e2facbc | ||
|
|
4969c2dc51 | ||
|
|
94cb60b1f7 | ||
|
|
33fcfd54ef | ||
|
|
10bb248e50 | ||
|
|
e023fd507e | ||
|
|
1a9f0f38dd | ||
|
|
02831a80bf | ||
|
|
9ccff1798e | ||
|
|
cd5ef8b5c6 | ||
|
|
dda1fdbbc2 | ||
|
|
3b49df5662 | ||
|
|
32be71ff8e | ||
|
|
3f77da1524 | ||
|
|
bae3e9e45d | ||
|
|
205f5b4196 | ||
|
|
12f12218e0 | ||
|
|
fae7e211a7 | ||
|
|
346da14431 | ||
|
|
15174f6c31 | ||
|
|
f29df040bb | ||
|
|
0bbecc16ec | ||
|
|
0c216161c2 | ||
|
|
8ad9c62eb5 | ||
|
|
4c99fbfd82 | ||
|
|
ed713e199c | ||
|
|
aacdb71c4a | ||
|
|
6968c3aae5 | ||
|
|
410fd93025 | ||
|
|
f2a0435dd2 | ||
|
|
c09c57da3f | ||
|
|
d66ec956a4 | ||
|
|
8229722735 | ||
|
|
dcd81a4db7 | ||
|
|
2617387417 | ||
|
|
56aee7d6e4 | ||
|
|
b4f07e8629 | ||
|
|
280cb34049 | ||
|
|
a21b0da928 | ||
|
|
8a0ba038f6 | ||
|
|
8e5c5f1450 | ||
|
|
b8f4f0c1ea | ||
|
|
17cb7fbd50 | ||
|
|
9963bc6a3f | ||
|
|
274943f383 | ||
|
|
be81755056 | ||
|
|
a5861c43f2 | ||
|
|
e3025a2da2 | ||
|
|
dd072bbdb6 | ||
|
|
1e3c736acf | ||
|
|
3c46cf2210 | ||
|
|
40cc1f4eeb | ||
|
|
3d880914d9 | ||
|
|
1162de9ee3 | ||
|
|
b117a1325d | ||
|
|
a809989536 | ||
|
|
a9ee3ca9ed | ||
|
|
56d972dd32 | ||
|
|
b4091264d7 | ||
|
|
23bf325a66 | ||
|
|
d9344a4c79 | ||
|
|
67d9a772e3 | ||
|
|
dae284e62e | ||
|
|
60f48bdc51 | ||
|
|
0d7885de29 | ||
|
|
d41d29fc1e | ||
|
|
3f400bd3f0 | ||
|
|
d43f664821 | ||
|
|
85c57c07da | ||
|
|
60d15447e0 | ||
|
|
6351b2d671 | ||
|
|
c7535ec509 | ||
|
|
67e17d073b | ||
|
|
c5cb4dcb6c | ||
|
|
0b9fe603a8 | ||
|
|
c7141892d1 | ||
|
|
c5556f9e2b | ||
|
|
1bc45c2ab2 | ||
|
|
38479e34aa | ||
|
|
87dfe96ee9 | ||
|
|
5bb4338d76 | ||
|
|
bb35fa56d6 | ||
|
|
fbab18b2fe | ||
|
|
edcdfb5937 | ||
|
|
f81efdb38c | ||
|
|
db34ded0a1 | ||
|
|
8a7a92a9b9 | ||
|
|
3eda32f9d7 | ||
|
|
c18c193ea7 | ||
|
|
79a8758448 |
No files matched your search
@@ -2,136 +2,90 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
servers-build:
|
||||
name: Release Build Server
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
tag-check:
|
||||
name: Check Release Tag
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make
|
||||
run: make servers
|
||||
|
||||
- name: Server Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
name: servers
|
||||
path: ./sliver-server_*
|
||||
fetch-depth: 0
|
||||
|
||||
clients-build:
|
||||
name: Release Build Client
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
- name: Find Semver Tag
|
||||
id: tag
|
||||
run: |
|
||||
git fetch --prune --tags -f
|
||||
tag=$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
release:
|
||||
name: Release
|
||||
needs: tag-check
|
||||
if: needs.tag-check.outputs.tag != ''
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make
|
||||
run: make clients
|
||||
|
||||
- name: Client Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
name: clients
|
||||
path: ./sliver-client_*
|
||||
fetch-depth: 0
|
||||
|
||||
tagged-release:
|
||||
needs: [servers-build, clients-build]
|
||||
|
||||
name: "Tagged Release"
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
path: ./builds
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/clients/* ./artifacts
|
||||
cp ./builds/servers/* ./artifacts
|
||||
- name: Install Minisign
|
||||
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
|
||||
|
||||
- name: GPG Key(s)
|
||||
- name: Build Clients
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
make clients
|
||||
mkdir -p artifacts
|
||||
mv sliver-client_* artifacts/
|
||||
|
||||
# Linux
|
||||
- name: Linux 386 (Client Only)
|
||||
- name: Build Servers
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-386.sig --detach-sig ./artifacts/sliver-client_linux-386
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-amd64.sig --detach-sig ./artifacts/sliver-server_linux-amd64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-amd64.sig --detach-sig ./artifacts/sliver-client_linux-amd64
|
||||
- name: Linux ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux-arm64.sig --detach-sig ./artifacts/sliver-server_linux-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux-arm64.sig --detach-sig ./artifacts/sliver-client_linux-arm64
|
||||
make servers
|
||||
mv sliver-server_* artifacts/
|
||||
|
||||
# Windows
|
||||
- name: Windows 386 (Client Only)
|
||||
- name: Sign Artifacts
|
||||
env:
|
||||
MINISIGN_KEY: ${{ secrets.MINISIGN_SLIVER_RELEASE_KEY }}
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-386.sig --detach-sig ./artifacts/sliver-client_windows-386.exe
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows-amd64.sig --detach-sig ./artifacts/sliver-server_windows-amd64.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-amd64.sig --detach-sig ./artifacts/sliver-client_windows-amd64.exe
|
||||
- name: Windows ARM64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows-arm64.sig --detach-sig ./artifacts/sliver-client_windows-arm64.exe
|
||||
echo "$MINISIGN_KEY" | base64 -d > minisign.key
|
||||
echo "minisign.key bytes: $(wc -c < minisign.key | tr -d ' ')"
|
||||
echo "minisign.key sha256: $(sha256sum minisign.key | awk '{print $1}')"
|
||||
chmod 600 minisign.key
|
||||
for file in artifacts/sliver-*; do
|
||||
[ -f "$file" ] || continue
|
||||
minisign -S -s minisign.key -m "$file" -x "$file.minisig" -W
|
||||
done
|
||||
rm -f minisign.key
|
||||
|
||||
# MacOS
|
||||
- name: MacOS AMD64
|
||||
- name: Create Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-amd64.sig --detach-sig ./artifacts/sliver-server_macos-amd64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-amd64.sig --detach-sig ./artifacts/sliver-client_macos-amd64
|
||||
- name: Package MacOS ARM64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos-arm64.sig --detach-sig ./artifacts/sliver-server_macos-arm64
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos-arm64.sig --detach-sig ./artifacts/sliver-client_macos-arm64
|
||||
tag="${{ needs.tag-check.outputs.tag }}"
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "Release $tag already exists."
|
||||
exit 0
|
||||
fi
|
||||
gh release create "$tag" --generate-notes
|
||||
|
||||
# FreeBSD
|
||||
- name: FreeBSD AMD64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-amd64.sig --detach-sig ./artifacts/sliver-client_freebsd-amd64.exe
|
||||
- name: FreeBSD ARM64 (Client Only)
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_freebsd-arm64.sig --detach-sig ./artifacts/sliver-client_freebsd-arm64.exe
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "bishopfox/action-gh-release@v1"
|
||||
with:
|
||||
files: |
|
||||
./artifacts/*
|
||||
- name: Upload Artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release upload "${{ needs.tag-check.outputs.tag }}" artifacts/* --clobber
|
||||
@@ -17,12 +17,12 @@ jobs:
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head of the pull request.
|
||||
@@ -36,10 +36,10 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
languages: go
|
||||
@@ -48,6 +48,6 @@ jobs:
|
||||
run: make
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
env:
|
||||
VERSION: ${{github.sha}}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Detect package manager
|
||||
id: detect-package-manager
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: ${{ steps.detect-package-manager.outputs.runner }} next build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/docs/sliver-docs/out
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ jobs:
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
@@ -34,38 +34,49 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Test Linux
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make Windows (amd64)
|
||||
run: make windows-amd64
|
||||
|
||||
- name: Make Linux (arm64)
|
||||
run: make linux-arm64
|
||||
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test Linux
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
|
||||
macos-build-test:
|
||||
name: MacOS Test
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -73,24 +84,37 @@ jobs:
|
||||
- name: Make MacOS (amd64)
|
||||
run: make macos-amd64
|
||||
|
||||
- name: Test MacOS (amd64)
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- 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 ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
clients-build:
|
||||
name: Clients Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.25"
|
||||
go-version: "1.25.6"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
/.devcontainer
|
||||
/.idea
|
||||
|
||||
/artifacts
|
||||
/artifacts/**
|
||||
|
||||
*.rc
|
||||
|
||||
/Dockerfile.*
|
||||
/*.sdk.tar.xz
|
||||
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
Sliver is a Go monorepo. Core client code lives in `client/` (CLI, assets, transports) and server code in `server/` (C2 services, assets, configs). `implant/` holds implant payload sources, while `util/` provides shared helpers that must stay server-side. Protocol buffers live under `protobuf/`, and documentation assets are in `docs/`. Keep generated artifacts (e.g., `sliver-client`, `sliver-server`, `.downloaded_assets`) out of commits.
|
||||
- `client/` holds the CLI client and interactive commands.
|
||||
- `server/` contains the server runtime, C2 logic, configs, and web assets.
|
||||
- `implant/` is the implant runtime and transport implementations.
|
||||
- `protobuf/` stores protobuf definitions and generated Go types.
|
||||
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
|
||||
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
|
||||
- `vendor/` is the vendored Go dependency tree used by builds.
|
||||
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
Use `make` targets to stay consistent:
|
||||
- `make` (or `make default`) compiles client/server binaries with vendored modules and tag defaults.
|
||||
- `make client` and `make linux-amd64` build platform-specific emitters; prefer `GOOS/GOARCH` env vars over editing sources.
|
||||
- `./go-assets.sh` refreshes client asset bundles; rerun when static files change.
|
||||
Regenerate protobuf bindings with `make pb`. For quick checks, `GOFLAGS="-tags client" go build ./client/...` is acceptable, but ensure the matching `server` build also succeeds.
|
||||
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
|
||||
- `make client` builds a CGO-free client; `make debug` keeps symbols.
|
||||
- `make pb` regenerates protobuf stubs from `protobuf/`.
|
||||
- `./go-tests.sh` runs the curated Go test suite with build tags.
|
||||
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
|
||||
- Go toolchain: the Makefile enforces Go >= 1.24.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Target Go 1.22+ (see `Makefile` validation). Format code with `gofmt` or `goimports` before committing. Keep package imports segregated (stdlib, third-party, internal). Use lower_snake filenames for assets, PascalCase for exported Go symbols, and prefer explicit names (`sessionID`, `operatorConfig`) over abbreviations. Never import `server` packages inside `client`. Avoid introducing CGO unless justified for cross-platform support.
|
||||
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
|
||||
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
|
||||
- Do not import `server` packages from `client`.
|
||||
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
|
||||
|
||||
## Testing Guidelines
|
||||
Run targeted unit tests with Go’s tooling and project tags, e.g. `go test -tags=client,go_sqlite ./client/command/...`. Full coverage uses `./build.py`; expect long runtimes and ensure ample CPU/RAM. Add tests beside code (`*_test.go`) and mirror package paths. When adding transports or implants, verify integration logs under `~/.sliver/logs/` for regressions and clean up fixtures.
|
||||
- Go tests follow standard `_test.go` naming within each package.
|
||||
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
|
||||
- Add or update tests when behavior changes; some server packages run longer tests.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
Sign every commit (`git commit -S`) and write imperative, scoped messages ("Fix DNS canary retry" rather than status updates). Keep vendor updates isolated in their own commit. Pull requests should describe the intent, affected modules, testing performed, and reference GitHub issues. Include before/after screenshots only when UI behavior changes (client console). Ensure CI passes and note any required follow-up.
|
||||
- Commits must be signed for PRs to `master`; work in a feature branch.
|
||||
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
|
||||
- Changes to `vendor/` should be in a distinct commit.
|
||||
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
|
||||
|
||||
## Security & Configuration Tips
|
||||
Default to secure settings: avoid `math/rand` for secrets, prefer vetted crypto (AES-GCM, Curve25519) per `CONTRIBUTING.md`. Lock down file permissions for generated assets and redact secrets from logs. When touching configuration, favor secure defaults even if it means additional setup for operators.
|
||||
## Security Notes
|
||||
- Default to secure configuration; fail closed rather than open.
|
||||
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
|
||||
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
|
||||
+5
-1
@@ -5,6 +5,7 @@
|
||||
#
|
||||
# For unit testing:
|
||||
# docker build --target test .
|
||||
# docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .
|
||||
#
|
||||
|
||||
# STAGE: base
|
||||
@@ -31,6 +32,9 @@ RUN cp -vv sliver-server /opt/sliver-server
|
||||
## Use `--target test` in the docker build command to run this stage
|
||||
FROM base AS test
|
||||
|
||||
ARG GO_TESTS_FLAGS=""
|
||||
ENV GO_TESTS_FLAGS="${GO_TESTS_FLAGS}"
|
||||
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
@@ -39,7 +43,7 @@ RUN apt-get update --fix-missing \
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
### Run unit tests
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh
|
||||
RUN /go/src/github.com/bishopfox/sliver/go-tests.sh ${GO_TESTS_FLAGS}
|
||||
|
||||
# STAGE: production
|
||||
## Final dockerized form of Sliver
|
||||
|
||||
@@ -16,49 +16,32 @@ endif
|
||||
# Prerequisites
|
||||
#
|
||||
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
|
||||
EXECUTABLES = uname sed git zip date cut $(GO)
|
||||
EXECUTABLES = uname sed git date cut $(GO)
|
||||
K := $(foreach exec,$(EXECUTABLES),\
|
||||
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
|
||||
|
||||
#
|
||||
# Version Information
|
||||
# Build Information
|
||||
#
|
||||
GO_VERSION = $(shell $(GO) version)
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
VERSION ?= $(shell git describe --abbrev=0)
|
||||
COMPILED_AT = $(shell date +%s)
|
||||
RELEASES_URL ?= https://api.github.com/repos/BishopFox/sliver/releases
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
|
||||
VERSION_PKG = github.com/bishopfox/sliver/client/version
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
|
||||
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
|
||||
GIT_COMMIT = $(shell git rev-parse HEAD)
|
||||
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
|
||||
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds shouldn't be stripped (-s -w flags)
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
@@ -83,7 +66,9 @@ endif
|
||||
# If no target is specified, determine GOARCH
|
||||
ifeq ($(UNAME_P),arm)
|
||||
ifeq ($(MAKECMDGOALS), )
|
||||
ENV += GOARCH=arm64
|
||||
ifeq ($(origin GOARCH), undefined)
|
||||
ENV += GOARCH=arm64
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -91,12 +76,6 @@ ifeq ($(MAKECMDGOALS), linux)
|
||||
# Redefine LDFLAGS to add the static part
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-extldflags '-static' \
|
||||
-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
endif
|
||||
@@ -105,9 +84,10 @@ endif
|
||||
# Targets
|
||||
#
|
||||
.PHONY: default
|
||||
default: clean .downloaded_assets validate-go-version
|
||||
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
default: clean validate-go-version
|
||||
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
|
||||
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
|
||||
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
|
||||
@@ -154,8 +134,9 @@ clients: clean .downloaded_assets validate-go-version
|
||||
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: servers
|
||||
servers:
|
||||
servers: clean .downloaded_assets validate-go-version
|
||||
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
|
||||
@@ -199,5 +180,5 @@ clean:
|
||||
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
|
||||
|
||||
.downloaded_assets:
|
||||
./go-assets.sh
|
||||
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
|
||||
touch ./.downloaded_assets
|
||||
@@ -6,12 +6,6 @@ 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)
|
||||
|
||||
# v1.6.0 / `master`
|
||||
|
||||
**NOTE:** You are looking at the latest master branch of Sliver v1.6.0; new PRs should target this branch. However, this branch is NOT RECOMMENDED for production use yet. Please use release tagged versions for the best experience.
|
||||
|
||||
For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x/master` branch](https://github.com/BishopFox/sliver/tree/v1.5.x/master).
|
||||
|
||||
### Features
|
||||
|
||||
- Dynamic code generation
|
||||
@@ -21,7 +15,7 @@ For PRs containing bug fixes specific to Sliver v1.5, please target the [`v1.5.x
|
||||
- [Procedurally generated C2](https://sliver.sh/docs?name=HTTPS+C2) over HTTP(S)
|
||||
- [DNS canary](https://sliver.sh/docs?name=DNS+C2) blue team detection
|
||||
- [Secure C2](https://sliver.sh/docs?name=Transport+Encryption) over mTLS, WireGuard, HTTP(S), and DNS
|
||||
- Fully scriptable using [JavaScript/TypeScript](https://github.com/moloch--/sliver-script) or [Python](https://github.com/moloch--/sliver-py)
|
||||
- Fully scriptable using [Python](https://github.com/moloch--/sliver-py)
|
||||
- Windows process migration, process injection, user token manipulation, etc.
|
||||
- Let's Encrypt integration
|
||||
- In-memory .NET assembly execution
|
||||
@@ -39,16 +33,13 @@ Download the latest [release](https://github.com/BishopFox/sliver/releases) and
|
||||
|
||||
### Help!
|
||||
|
||||
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions). We also tend to hang out in the #golang Slack channel on the [Bloodhound Gang](https://bloodhoundgang.herokuapp.com/) server.
|
||||
Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](https://github.com/BishopFox/sliver/discussions).
|
||||
|
||||
### Compile From Source
|
||||
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
|
||||
|
||||
### License - GPLv3
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
INFO = "\033[1m\033[36m[*]\033[0m "
|
||||
WARN = "\033[1m\033[31m[!]\033[0m "
|
||||
BOLD = "\033[1m"
|
||||
NORM = "\033[0m"
|
||||
|
||||
|
||||
def exec_cmd(cmd, ignore_status=False):
|
||||
"""Executes a command with some sugar"""
|
||||
print("\n" + INFO + " %s\n" % cmd)
|
||||
status = os.system(cmd)
|
||||
if status != 0 and not ignore_status:
|
||||
print("\n" + WARN + "Command did not exit cleanly (%s) " % status)
|
||||
if input("continue? [Y/n]: ").upper() != "Y":
|
||||
sys.exit(status)
|
||||
return status
|
||||
|
||||
|
||||
def docker_rm_volumes():
|
||||
exec_cmd("docker volume rm $(docker volume ls -q)", ignore_status=True)
|
||||
|
||||
|
||||
def docker_rm_containers():
|
||||
exec_cmd("docker rm $(docker ps -a -q)", ignore_status=True)
|
||||
|
||||
|
||||
def docker_rm_images():
|
||||
exec_cmd("docker rmi -f $(docker images -q)", ignore_status=True)
|
||||
|
||||
|
||||
def docker_prune():
|
||||
exec_cmd("docker image prune -f", ignore_status=True)
|
||||
|
||||
|
||||
def docker_rm_all():
|
||||
docker_rm_containers()
|
||||
docker_rm_images()
|
||||
docker_rm_volumes()
|
||||
|
||||
|
||||
def build():
|
||||
status = exec_cmd("docker build --target test .")
|
||||
if status == 0:
|
||||
print("\n\n" + INFO + "Build successful")
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Execute the respective task(s) based on cli args"""
|
||||
if args.rm_images:
|
||||
docker_rm_images()
|
||||
if args.rm_containers:
|
||||
docker_rm_containers()
|
||||
if args.rm_volumes:
|
||||
docker_rm_volumes()
|
||||
if args.rm_all:
|
||||
docker_rm_all()
|
||||
|
||||
if not args.no_prune:
|
||||
docker_prune()
|
||||
if not args.no_build:
|
||||
build()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="automates interactions with docker")
|
||||
|
||||
parser.add_argument(
|
||||
"--no-build",
|
||||
"-b",
|
||||
help="do not run build",
|
||||
dest="no_build",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-prune",
|
||||
"-np",
|
||||
help="do not prune images",
|
||||
dest="no_prune",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--rm-all",
|
||||
"-rma",
|
||||
help="rm all containers, images, and volumes",
|
||||
dest="rm_all",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rm-containers",
|
||||
"-rmc",
|
||||
help="rm all containers",
|
||||
dest="rm_containers",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rm-images",
|
||||
"-rmi",
|
||||
help="rm all images",
|
||||
dest="rm_images",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rm-volumes",
|
||||
"-rmv",
|
||||
help="rm dangling volumes only",
|
||||
dest="rm_volumes",
|
||||
action="store_true",
|
||||
)
|
||||
try:
|
||||
main(parser.parse_args())
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n" + WARN + "User stopped process")
|
||||
+23
-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 {
|
||||
@@ -73,6 +80,18 @@ func GetConsoleLogsDir() string {
|
||||
return consoleLogsDir
|
||||
}
|
||||
|
||||
// GetMCPLogsDir - Get the Sliver client MCP logs dir ~/.sliver-client/logs/mcp/
|
||||
func GetMCPLogsDir() string {
|
||||
mcpLogsDir := filepath.Join(GetClientLogsDir(), "mcp")
|
||||
if _, err := os.Stat(mcpLogsDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(mcpLogsDir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return mcpLogsDir
|
||||
}
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := os.ReadFile(filepath.Join(appDir, versionFileName))
|
||||
@@ -96,7 +115,7 @@ func Setup(force bool, echo bool) {
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf(`
|
||||
Sliver Copyright (C) 2022 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.`)
|
||||
@@ -104,7 +123,5 @@ under certain conditions; type 'licenses' for details.`)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+39
-16
@@ -22,35 +22,58 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
settingsFileName = "tui-settings.yaml"
|
||||
settingsLegacyFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if err := SaveSettings(settings); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
@@ -73,7 +96,7 @@ func SaveSettings(settings *ClientSettings) error {
|
||||
if settings == nil {
|
||||
settings = defaultSettings()
|
||||
}
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
data, err := yaml.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
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
|
||||
`)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
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 _, 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)
|
||||
}
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -69,6 +70,9 @@ func init() {
|
||||
// other command trees below, such as the implant one.
|
||||
rootCmd.AddCommand(consoleCmd(con))
|
||||
|
||||
// MCP stdio server.
|
||||
rootCmd.AddCommand(mcpCmd(con))
|
||||
|
||||
// Implant.
|
||||
// The implant command allows users to run commands on slivers from their
|
||||
// system shell. It makes use of pre-runners for connecting to the server
|
||||
|
||||
+10
-24
@@ -23,7 +23,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
)
|
||||
|
||||
func selectConfig() *assets.ClientConfig {
|
||||
@@ -39,32 +39,18 @@ func selectConfig() *assets.ClientConfig {
|
||||
}
|
||||
}
|
||||
|
||||
answer := struct{ Config string }{}
|
||||
qs := getPromptForConfigs(configs)
|
||||
err := survey.Ask(qs, &answer)
|
||||
keys := make([]string, 0, len(configs))
|
||||
for key := range configs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
selection := keys[0]
|
||||
err := forms.Select("Select a server:", keys, &selection)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return configs[answer.Config]
|
||||
}
|
||||
|
||||
func getPromptForConfigs(configs map[string]*assets.ClientConfig) []*survey.Question {
|
||||
keys := []string{}
|
||||
for k := range configs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
return []*survey.Question{
|
||||
{
|
||||
Name: "config",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Select a server:",
|
||||
Options: keys,
|
||||
Default: keys[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
return configs[selection]
|
||||
}
|
||||
@@ -40,6 +40,7 @@ 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
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
|
||||
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 {
|
||||
@@ -59,13 +60,18 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
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
|
||||
|
||||
rpc, ln, err = transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
@@ -76,7 +82,7 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
// 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, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
|
||||
}
|
||||
|
||||
// Close the RPC connection once exiting
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package cli
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
clientmcp "github.com/bishopfox/sliver/client/mcp"
|
||||
"github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func mcpCmd(con *console.SliverClient) *cobra.Command {
|
||||
mcpCmd := &cobra.Command{
|
||||
Use: "mcp",
|
||||
Short: "Start the MCP stdio server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runMCP(cmd, con)
|
||||
},
|
||||
}
|
||||
mcpCmd.Flags().String("config", "", "path to client config file")
|
||||
return mcpCmd
|
||||
}
|
||||
|
||||
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
|
||||
configPath, _ := cmd.Flags().GetString("config")
|
||||
config, err := loadMCPClientConfig(configPath)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rpc, ln, err := transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s\n", err)
|
||||
return nil
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
con.Rpc = rpc
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
cfg := clientmcp.DefaultConfig()
|
||||
if err := clientmcp.ServeStdio(cfg, con.Rpc); err != nil {
|
||||
fmt.Printf("MCP server error: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMCPClientConfig(path string) (*assets.ClientConfig, error) {
|
||||
if path != "" {
|
||||
configPath := path
|
||||
if !filepath.IsAbs(configPath) {
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
configPath = filepath.Join(assets.GetConfigDir(), configPath)
|
||||
}
|
||||
}
|
||||
return assets.ReadConfig(configPath)
|
||||
}
|
||||
|
||||
configs := assets.GetConfigs()
|
||||
switch len(configs) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("no config files found at %s (use --config)", assets.GetConfigDir())
|
||||
case 1:
|
||||
for _, config := range configs {
|
||||
return config, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("multiple configs found; use --config to select one")
|
||||
}
|
||||
return nil, fmt.Errorf("no config files found")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
|
||||
|
||||
- `command.go` – Provides helpers for binding Cobra commands, flag sets, and visibility filters into the client command tree.
|
||||
- `server.go` – Defines the server menu composition, wiring each server-side command group onto the root console structure at runtime.
|
||||
- `sliver.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
|
||||
- `main.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
|
||||
|
||||
## Sub-packages
|
||||
|
||||
@@ -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.
|
||||
@@ -40,6 +41,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
|
||||
- `licenses/` – Implements the 'licenses' command group for the Sliver client console.
|
||||
- `loot/` – Implements the 'loot' command group for the Sliver client console.
|
||||
- `monitor/` – Implements the 'monitor' command group for the Sliver client console.
|
||||
- `mcp/` – Implements the 'mcp' command group for the Sliver client console.
|
||||
- `network/` – Implements the 'network' command group for the Sliver client console.
|
||||
- `operators/` – Implements the 'operators' command group for the Sliver client console.
|
||||
- `pivots/` – Implements the 'pivots' command group for the Sliver client console.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# client/command/aka
|
||||
|
||||
## Overview
|
||||
|
||||
Implements the 'aka' command group for the Sliver client console. "Aka"s are
|
||||
command aliases (like 'alias' in 'bash') that allow operators to specify
|
||||
shorthand commands and default arguments for commonly used commands.
|
||||
|
||||
### Examples:
|
||||
|
||||
- `aka create bg background`: Allows an operator to run `bg` from the implant
|
||||
menu to quickly move back to the server menu
|
||||
- `aka create execw execute -o "C:\Windows\System32\cmd.exe" /c`: Shorthand for
|
||||
executing binaries on Windows targets like: `execw arp -a`
|
||||
|
||||
## Go Files
|
||||
|
||||
- `commands.go` – Wires Cobra commands for listing, creating, and deleting command aliases in the client console.
|
||||
- `create.go` – Handles creating command aliases to use by the client.
|
||||
- `delete.go` – Removes command aliases currently tracked by the client.
|
||||
- `hook.go` – The main hooking logic to intercept commands ran by the client console. The hook will intercept processing before hitting the Cobra tree to transform the command alias to a full command with default arguments from the command alias and user-provided arguments to the alias.
|
||||
- `list.go` - Lists the current command aliases tracked by the client.
|
||||
- `storage.go` - Supports the saving of aliases to disk to keep between runs of the client.
|
||||
@@ -0,0 +1,59 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func ServerCommands(con *console.SliverClient) []*cobra.Command {
|
||||
return commands(con, constants.GenericHelpGroup)
|
||||
}
|
||||
|
||||
func ImplantCommands(con *console.SliverClient) []*cobra.Command {
|
||||
return commands(con, constants.SliverCoreHelpGroup)
|
||||
}
|
||||
|
||||
func commands(con *console.SliverClient, group string) []*cobra.Command {
|
||||
akaCommand := &cobra.Command{
|
||||
Use: "aka",
|
||||
Short: "Manage command aliases (aka's)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaListCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: group,
|
||||
}
|
||||
|
||||
createAka := &cobra.Command{
|
||||
Use: "create <alias> <command> [args...]",
|
||||
Short: "Create a new command alias (aka)",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
DisableFlagParsing: true, // No flags with aka create so we capture flags to other commands
|
||||
DisableFlagsInUseLine: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaCreateCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
createAka.InitDefaultHelpFlag()
|
||||
createAka.Flags().Lookup("help").Hidden = true
|
||||
akaCommand.AddCommand(createAka)
|
||||
|
||||
deleteAka := &cobra.Command{
|
||||
Use: "delete <alias>",
|
||||
Short: "Delete a command alias (aka)",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AkaDeleteCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
akaCommand.AddCommand(deleteAka)
|
||||
|
||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, Cmdhook)
|
||||
|
||||
err := LoadAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to load aka alises: %s\n", err)
|
||||
}
|
||||
|
||||
return []*cobra.Command{akaCommand}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaCreateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
aliasName := args[0]
|
||||
command := args[1]
|
||||
defaultArgs := args[2:] // anything else is going to be the default args to the alias
|
||||
|
||||
if _, exists := akaAliases[aliasName]; exists {
|
||||
con.PrintErrorf("Alias '%s' already exists\n", aliasName)
|
||||
return
|
||||
}
|
||||
|
||||
var desc string
|
||||
if len(defaultArgs) == 0 {
|
||||
desc = fmt.Sprintf("%s", command)
|
||||
} else {
|
||||
desc = fmt.Sprintf("%s %s", command, strings.Join(defaultArgs, " "))
|
||||
}
|
||||
|
||||
alias := &AkaAlias{
|
||||
Name: aliasName,
|
||||
Command: command,
|
||||
DefaultArgs: defaultArgs,
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
akaAliases[aliasName] = alias
|
||||
|
||||
// save this new alias to disk
|
||||
err := SaveAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save alias '%s' to disk: %s\n", aliasName, err)
|
||||
// we still technically added it to the map, so the alias did get created in
|
||||
// memory so we should still print the Info message below
|
||||
}
|
||||
|
||||
con.PrintInfof("Create alias for '%s' -> '%v'\n", aliasName, alias.Description)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaDeleteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
aliasName := args[0]
|
||||
if _, exists := akaAliases[aliasName]; !exists {
|
||||
con.PrintErrorf("Alias '%s' does not exist\n", aliasName)
|
||||
return
|
||||
}
|
||||
|
||||
delete(akaAliases, aliasName)
|
||||
|
||||
// Save updated map to disk
|
||||
err := SaveAkaAliases()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save aliases to disk: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Deleted alias '%s'\n", aliasName)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Cmdhook(args []string) ([]string, error) {
|
||||
if len(args) == 0 {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
if aliasArgs, exists := akaAliases[args[0]]; exists {
|
||||
var expandedArgs []string
|
||||
|
||||
expandedArgs = append(expandedArgs, aliasArgs.Command)
|
||||
expandedArgs = append(expandedArgs, aliasArgs.DefaultArgs...)
|
||||
// join the user passed args so the shell processes it without flags if
|
||||
// available
|
||||
if len(args[1:]) > 0 {
|
||||
expandedArgs = append(expandedArgs, strings.Join(args[1:], " "))
|
||||
}
|
||||
|
||||
return expandedArgs, nil
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func AkaListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if len(akaAliases) == 0 {
|
||||
con.PrintInfof("No command aliases created. Use `aka create` to create one.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Alias",
|
||||
"Command",
|
||||
})
|
||||
|
||||
for alias, cmd := range akaAliases {
|
||||
tw.AppendRow(table.Row{
|
||||
alias,
|
||||
cmd.Description,
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package aka
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
)
|
||||
|
||||
type AkaAlias struct {
|
||||
Name string `json:"name"` // alias Name
|
||||
Command string `json:"command"` // command being aliased
|
||||
DefaultArgs []string `json:"default_args"` // args passed by the user
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var akaAliases = make(map[string]*AkaAlias)
|
||||
|
||||
const (
|
||||
akaAliasFileName = "aka-aliases.json"
|
||||
)
|
||||
|
||||
func GetAkaAliasesFilePath() string {
|
||||
return filepath.Join(assets.GetRootAppDir(), akaAliasFileName)
|
||||
}
|
||||
|
||||
func LoadAkaAliases() error {
|
||||
filepath := GetAkaAliasesFilePath()
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
// if file hasn't been created yet, we fail gracefully
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
// otherwise, return the actual error; something else went wrong
|
||||
return err
|
||||
}
|
||||
|
||||
aliases := []AkaAlias{}
|
||||
err = json.Unmarshal(data, &aliases)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
akaAliases = make(map[string]*AkaAlias)
|
||||
|
||||
for _, alias := range aliases {
|
||||
akaAliases[alias.Name] = &alias
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveAkaAliases() error {
|
||||
filepath := GetAkaAliasesFilePath()
|
||||
aliases := make([]*AkaAlias, 0, len(akaAliases))
|
||||
for _, alias := range akaAliases {
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(aliases, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filepath, data, 0o600)
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -62,8 +62,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current install?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -122,8 +121,7 @@ func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current install?", &confirm)
|
||||
if !confirm {
|
||||
return nil
|
||||
}
|
||||
|
||||
+173
-11
@@ -27,15 +27,16 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"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,8 +374,7 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
con.PrintWarnf("%s", msgStr)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Do you want to continue?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
forms.Confirm("Do you want to continue?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -590,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")
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -39,8 +39,7 @@ func AliasesRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' alias?", name), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Use: consts.InstallStr,
|
||||
Short: "Install an alias or extension",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryInstallCmd(cmd, con, args)
|
||||
},
|
||||
|
||||
@@ -26,8 +26,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -35,6 +36,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
@@ -51,10 +53,9 @@ const (
|
||||
// ArmoryInstallCmd - The armory install command
|
||||
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var promptToOverwrite bool
|
||||
name := args[0]
|
||||
if name == "" {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
names := []string{}
|
||||
if len(args) > 0 {
|
||||
names = []string{args[0]}
|
||||
}
|
||||
forceInstallation, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
@@ -83,51 +84,92 @@ func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
if name == "all" {
|
||||
aliases, extensions := packageManifestsInCache()
|
||||
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
|
||||
confirm := false
|
||||
pluralAliases := "es"
|
||||
if aliasCount == 1 {
|
||||
pluralAliases = ""
|
||||
|
||||
if len(names) == 0 {
|
||||
armoryFilterName := ""
|
||||
if armoryPK != "" {
|
||||
armoryFilterName = armoryName
|
||||
}
|
||||
pluralExtensions := "s"
|
||||
if extCount == 1 {
|
||||
pluralExtensions = ""
|
||||
}
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Install %d alias%s and %d extension%s?",
|
||||
aliasCount, pluralAliases, extCount, pluralExtensions,
|
||||
),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
options := armoryInstallOptions(armoryPK, armoryFilterName)
|
||||
if len(options) == 0 {
|
||||
con.PrintInfof("No packages or bundles found\n")
|
||||
return
|
||||
}
|
||||
promptToOverwrite = false
|
||||
}
|
||||
err = installPackageByName(name, armoryPK, forceInstallation, promptToOverwrite, clientConfig, con)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ErrPackageNotFound) {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
|
||||
result, err := forms.ArmoryInstallForm(options)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Armory install form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
// If we have made it here, then there was not a bundle or package that matched the provided name
|
||||
if armoryPK == "" {
|
||||
con.PrintErrorf("No package or bundle named %q was found\n", name)
|
||||
if len(result.SelectedNames) == 0 {
|
||||
return
|
||||
}
|
||||
names = result.SelectedNames
|
||||
}
|
||||
if len(names) == 0 {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
}
|
||||
if slices.Contains(names, "all") {
|
||||
names = []string{"all"}
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
promptOverwrite := promptToOverwrite
|
||||
if name == "all" {
|
||||
aliases, extensions := packageManifestsInCache()
|
||||
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
|
||||
confirm := false
|
||||
pluralAliases := "es"
|
||||
if aliasCount == 1 {
|
||||
pluralAliases = ""
|
||||
}
|
||||
pluralExtensions := "s"
|
||||
if extCount == 1 {
|
||||
pluralExtensions = ""
|
||||
}
|
||||
forms.Confirm(fmt.Sprintf("Install %d alias%s and %d extension%s?",
|
||||
aliasCount, pluralAliases, extCount, pluralExtensions,
|
||||
), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
promptOverwrite = false
|
||||
}
|
||||
|
||||
err = installPackageByName(name, armoryPK, forceInstallation, promptOverwrite, clientConfig, con)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, ErrPackageNotFound) {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
// If we have made it here, then there was not a bundle or package that matched the provided name
|
||||
if armoryPK == "" {
|
||||
con.PrintErrorf("No package or bundle named %q was found\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
|
||||
}
|
||||
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
|
||||
con.PrintErrorf("Could not install package: %s\n", err)
|
||||
}
|
||||
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("Could not install package: %s\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -232,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)
|
||||
}
|
||||
}
|
||||
@@ -296,11 +341,7 @@ func getPackageIDFromUser(name string, options map[string]string) string {
|
||||
// Add a cancel option
|
||||
optionKeys = append(optionKeys, doNotInstallOption)
|
||||
options[doNotInstallOption] = doNotInstallPackageName
|
||||
prompt := &survey.Select{
|
||||
Message: fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name),
|
||||
Options: optionKeys,
|
||||
}
|
||||
survey.AskOne(prompt, &selectedPackageKey)
|
||||
forms.Select(fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name), optionKeys, &selectedPackageKey)
|
||||
selectedPackageID := options[selectedPackageKey]
|
||||
|
||||
return selectedPackageID
|
||||
@@ -360,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
|
||||
@@ -420,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)
|
||||
@@ -444,12 +494,114 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
|
||||
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("Operation complete\n")
|
||||
con.PrintSuccessf("Operation complete\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func armoryInstallOptions(armoryPK, armoryName string) []forms.ArmoryInstallOption {
|
||||
options := []forms.ArmoryInstallOption{}
|
||||
seen := make(map[string]bool)
|
||||
packageCount := 0
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr != nil {
|
||||
return true
|
||||
}
|
||||
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
|
||||
return true
|
||||
}
|
||||
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
name := cacheEntry.Alias.CommandName
|
||||
if name == "" {
|
||||
return true
|
||||
}
|
||||
if seen[name] {
|
||||
return true
|
||||
}
|
||||
label := formatArmoryInstallLabel("alias", name, cacheEntry.Alias.Help)
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: name,
|
||||
Label: label,
|
||||
})
|
||||
seen[name] = true
|
||||
packageCount++
|
||||
return true
|
||||
}
|
||||
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
name := command.CommandName
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
label := formatArmoryInstallLabel("extension", name, command.Help)
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: name,
|
||||
Label: label,
|
||||
})
|
||||
seen[name] = true
|
||||
packageCount++
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
for _, bundle := range bundlesInCache() {
|
||||
if bundle.Name == "" {
|
||||
continue
|
||||
}
|
||||
if armoryName != "" && bundle.ArmoryName != armoryName {
|
||||
continue
|
||||
}
|
||||
if seen[bundle.Name] {
|
||||
continue
|
||||
}
|
||||
label := fmt.Sprintf("bundle: %s (%d packages)", bundle.Name, len(bundle.Packages))
|
||||
options = append(options, forms.ArmoryInstallOption{
|
||||
Value: bundle.Name,
|
||||
Label: label,
|
||||
})
|
||||
seen[bundle.Name] = true
|
||||
}
|
||||
|
||||
if len(options) == 0 {
|
||||
return options
|
||||
}
|
||||
|
||||
sort.Slice(options, func(i, j int) bool {
|
||||
return options[i].Label < options[j].Label
|
||||
})
|
||||
|
||||
if packageCount > 0 {
|
||||
options = append([]forms.ArmoryInstallOption{
|
||||
{
|
||||
Value: "all",
|
||||
Label: "all packages (aliases + extensions)",
|
||||
},
|
||||
}, options...)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func formatArmoryInstallLabel(kind, name, help string) string {
|
||||
help = strings.TrimSpace(help)
|
||||
if help == "" {
|
||||
return fmt.Sprintf("%s: %s", kind, name)
|
||||
}
|
||||
help = strings.Join(strings.Fields(help), " ")
|
||||
return fmt.Sprintf("%s: %s - %s", kind, name, help)
|
||||
}
|
||||
|
||||
func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
@@ -483,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")
|
||||
}
|
||||
@@ -577,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
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -602,8 +602,7 @@ func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not modify armory: %s\n", err)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Would you like to revert the properties of the armory back to their previous values?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("Would you like to revert the properties of the armory back to their previous values?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
} else {
|
||||
|
||||
@@ -19,13 +19,11 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -34,6 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
)
|
||||
|
||||
@@ -87,16 +86,19 @@ func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
|
||||
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
|
||||
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
|
||||
selectedUpdates, err = getUpdatesFromUser(con, updateKeys)
|
||||
selectedUpdates, err = getUpdatesFromUser(updateKeys)
|
||||
if err != nil {
|
||||
con.PrintErrorf(err.Error() + "\n")
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
}
|
||||
if len(selectedUpdates) == 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
con.PrintSuccessf("All packages are up to date")
|
||||
con.PrintSuccessf("All packages are up to date\n")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -299,76 +301,35 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
func getUpdatesFromUser(con *console.SliverClient, updateKeys []UpdateIdentifier) (chosenUpdates []UpdateIdentifier, selectionError error) {
|
||||
chosenUpdates = []UpdateIdentifier{}
|
||||
func getUpdatesFromUser(updateKeys []UpdateIdentifier) ([]UpdateIdentifier, error) {
|
||||
options := make([]forms.ArmoryUpdateOption, 0, len(updateKeys))
|
||||
optionIndex := make(map[string]UpdateIdentifier, len(updateKeys))
|
||||
|
||||
var updateResponse string
|
||||
for _, key := range updateKeys {
|
||||
packageType := "Alias"
|
||||
if key.Type == ExtensionPackage {
|
||||
packageType = "Extension"
|
||||
}
|
||||
optionID := fmt.Sprintf("%d:%s", key.Type, key.Name)
|
||||
optionLabel := fmt.Sprintf("%s (%s)", key.Name, packageType)
|
||||
options = append(options, forms.ArmoryUpdateOption{
|
||||
ID: optionID,
|
||||
Label: optionLabel,
|
||||
})
|
||||
optionIndex[optionID] = key
|
||||
}
|
||||
|
||||
con.Println("You can apply all, none, or some updates.\nTo apply some updates, specify the number of a single update, a range (1-3), or a combination of the two (1, 3-5, 7)\n")
|
||||
err := survey.AskOne(&survey.Input{
|
||||
Message: "Which updates would you like to apply? [A]ll, [N]one, or some:",
|
||||
}, &updateResponse)
|
||||
result, err := forms.ArmoryUpdateForm(options)
|
||||
if err != nil {
|
||||
// We do not need to set selectionError because an error here means the same thing as responding with "none"
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateResponse = strings.ToLower(updateResponse)
|
||||
updateResponse = strings.Replace(updateResponse, " ", "", -1)
|
||||
if updateResponse == "n" || updateResponse == "none" {
|
||||
return
|
||||
}
|
||||
|
||||
if updateResponse == "a" || updateResponse == "all" {
|
||||
chosenUpdates = updateKeys
|
||||
return
|
||||
}
|
||||
|
||||
selections := strings.Split(updateResponse, ",")
|
||||
|
||||
for _, selection := range selections {
|
||||
// Handle a range
|
||||
if strings.Contains(selection, "-") {
|
||||
rangeParts := strings.Split(selection, "-")
|
||||
start, err := strconv.Atoi(rangeParts[0])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[0])
|
||||
return
|
||||
}
|
||||
end, err := strconv.Atoi(rangeParts[1])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[1])
|
||||
return
|
||||
}
|
||||
// Adjust for the 0 indexed slice we are working with
|
||||
start -= 1
|
||||
end -= 1
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > end {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
if end >= len(updateKeys) {
|
||||
end = len(updateKeys) - 1
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[i])
|
||||
}
|
||||
} else {
|
||||
// Single entry
|
||||
index, err := strconv.Atoi(selection)
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
index -= 1
|
||||
if index >= 0 && index < len(updateKeys) {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[index])
|
||||
}
|
||||
chosenUpdates := make([]UpdateIdentifier, 0, len(result.SelectedIDs))
|
||||
for _, selectedID := range result.SelectedIDs {
|
||||
if update, ok := optionIndex[selectedID]; ok {
|
||||
chosenUpdates = append(chosenUpdates, update)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return chosenUpdates, nil
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -26,9 +26,8 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
@@ -83,12 +82,8 @@ func SelectBeacon(con *console.SliverClient) (*clientpb.Beacon, error) {
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a beacon:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
_ = forms.Select("Select a beacon:", options, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ package beacons
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
@@ -71,8 +71,7 @@ func BeaconsPruneCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
con.Println()
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Prune these beacons?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Prune these beacons?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package c2profiles
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -38,10 +38,15 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
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,20 +553,20 @@ 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 {
|
||||
choices = append(choices, c2profile.Name)
|
||||
}
|
||||
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a c2 profile",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &c2profile, nil)
|
||||
_ = forms.Select("Select a c2 profile", choices, &c2profile)
|
||||
|
||||
return c2profile
|
||||
if c2profile == "" {
|
||||
return "", ErrNoSelection
|
||||
}
|
||||
|
||||
return c2profile, nil
|
||||
}
|
||||
|
||||
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
|
||||
@@ -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,90 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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 {
|
||||
rowColor := console.Normal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -21,10 +21,10 @@ package clean
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -35,8 +35,7 @@ import (
|
||||
func CleanCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.Printf("This command will kill and remove all sessions, beacons and profiles \n")
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Are you sure you want to destroy everything?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Are you sure you want to destroy everything?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,6 +24,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 +35,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{
|
||||
|
||||
@@ -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(), "_")
|
||||
}
|
||||
@@ -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,7 +129,7 @@ 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")
|
||||
@@ -131,8 +164,10 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -140,9 +175,17 @@ func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
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])
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
@@ -55,11 +55,7 @@ func SelectCredential(plaintext bool, hashType clientpb.HashType, con *console.S
|
||||
}
|
||||
|
||||
selected := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a credential:",
|
||||
Options: options,
|
||||
}
|
||||
err = survey.AskOne(prompt, &selected)
|
||||
err = forms.Select("Select a credential:", options, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,19 +23,19 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Chrome process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -213,7 +213,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, cmd *cobra
|
||||
con.PrintInfof("Waiting for %s process to initialize ... ", name)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindPort := util.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/reeflective/readline"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -71,12 +71,8 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a debug target:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
err := forms.Select("Select a debug target:", options, &selected)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
@@ -106,7 +106,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
|
||||
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
|
||||
@@ -22,18 +22,18 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
insecureRand "math/rand"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/bishopfox/sliver/client/tcpproxy"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -88,7 +88,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
|
||||
con.Printf("\n")
|
||||
confirm := false
|
||||
err = survey.AskOne(&survey.Confirm{Message: "Kill and restart the process?"}, &confirm)
|
||||
err = forms.Confirm("Kill and restart the process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -118,7 +118,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
|
||||
return curse
|
||||
}
|
||||
|
||||
func checkElectronPath(electronExe string, session *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (bool, error) {
|
||||
func checkElectronPath(electronExe string, _ *clientpb.Session, cmd *cobra.Command, con *console.SliverClient) (bool, error) {
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: electronExe,
|
||||
@@ -175,7 +175,7 @@ func startCursedElectronProcess(electronExe string, session *clientpb.Session, c
|
||||
con.PrintInfof("Waiting for process to initialize ... ")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
bindPort := insecureRand.Intn(10000) + 40000
|
||||
bindPort := util.Intn(10000) + 40000
|
||||
bindAddr := fmt.Sprintf("127.0.0.1:%d", bindPort)
|
||||
|
||||
remoteAddr := fmt.Sprintf("127.0.0.1:%d", debugPort)
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -44,7 +44,7 @@ func CursedRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
core.CloseCursedProcessesByBindPort(session.ID, bindPort)
|
||||
if kill {
|
||||
confirm := false
|
||||
err := survey.AskOne(&survey.Confirm{Message: "Kill the cursed process?"}, &confirm)
|
||||
err := forms.Confirm("Kill the cursed process?", &confirm)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
|
||||
@@ -21,15 +21,15 @@ package cursed
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -95,12 +95,8 @@ func selectCursedProcess(con *console.SliverClient) *core.CursedProcess {
|
||||
table.Flush()
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a curse:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
err := survey.AskOne(prompt, &selected)
|
||||
err := forms.Select("Select a curse:", options, &selected)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil
|
||||
@@ -115,7 +111,7 @@ func selectCursedProcess(con *console.SliverClient) *core.CursedProcess {
|
||||
func getRemoteDebuggerPort(cmd *cobra.Command) int {
|
||||
port, _ := cmd.Flags().GetInt("remote-debugging-port")
|
||||
if port == 0 {
|
||||
port = insecureRand.Intn(30000) + 10000
|
||||
port = util.Intn(30000) + 10000
|
||||
}
|
||||
return port
|
||||
}
|
||||
@@ -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,380 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"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"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
"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)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
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
@@ -4,6 +4,7 @@ import (
|
||||
"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 +26,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", true, "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 +46,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)",
|
||||
@@ -169,11 +187,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{
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -82,8 +82,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
if len(assemblyArgsStr) > 256 && !inProcess {
|
||||
con.PrintWarnf(" Injected .NET assembly arguments are limited to 256 characters when using the default fork/exec model.\nConsider using the --in-process flag to execute the .NET assembly in-process and work around this limitation.\n")
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Do you want to continue?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("Do you want to continue?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -157,10 +157,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 +215,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)
|
||||
|
||||
@@ -44,35 +44,53 @@ 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)
|
||||
return
|
||||
}
|
||||
if envInheritance || len(envVars) > 0 {
|
||||
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
|
||||
return
|
||||
}
|
||||
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -81,12 +99,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 +162,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 +212,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
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,6 +31,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -175,8 +175,8 @@ func PsExecCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
func randomString() string {
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
str := ""
|
||||
for index := 0; index < insecureRand.Intn(8)+1; index++ {
|
||||
str += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(8)+1; index++ {
|
||||
str += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func randomString() string {
|
||||
func randomFileName() string {
|
||||
noun := randomString()
|
||||
noun = strings.ToLower(noun)
|
||||
switch insecureRand.Intn(3) {
|
||||
switch util.Intn(3) {
|
||||
case 0:
|
||||
noun = strings.ToUpper(noun)
|
||||
case 1:
|
||||
@@ -192,16 +192,16 @@ func randomFileName() string {
|
||||
}
|
||||
|
||||
separators := []string{"", "", "", "", "", ".", "-", "_", "--", "__"}
|
||||
sep := separators[insecureRand.Intn(len(separators))]
|
||||
sep := separators[util.Intn(len(separators))]
|
||||
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
prefix := ""
|
||||
for index := 0; index < insecureRand.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
suffix := ""
|
||||
for index := 0; index < insecureRand.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
for index := 0; index < util.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[util.Intn(len(alphanumeric))])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s%s%s", prefix, sep, noun, sep, suffix)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -145,8 +145,7 @@ func tryCredsFromLoot(con *console.SliverClient) (string, string, []byte) {
|
||||
privKey []byte
|
||||
)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "No credentials provided, use from loot?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("No credentials provided, use from loot?", &confirm)
|
||||
if confirm {
|
||||
// cred, err := loot.SelectCredentials(con)
|
||||
// if err != nil {
|
||||
|
||||
@@ -20,39 +20,36 @@ package exit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// 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
|
||||
prompt := &survey.Confirm{Message: "Are you sure you want to exit?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
forms.Confirm("Are you sure you want to exit?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
flushAndExit(con, 0)
|
||||
}
|
||||
|
||||
// Commands returns the `exit` command.
|
||||
@@ -66,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)
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -92,8 +92,7 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current install?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,21 +30,20 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"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) {
|
||||
@@ -171,8 +171,7 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
if CmdExists(extCmd.CommandName, sliverMenu.Command) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current command?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite current command?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -690,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)
|
||||
@@ -838,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)
|
||||
@@ -859,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 {
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -40,8 +40,7 @@ func ExtensionsRemoveCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension?", name), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -104,8 +103,7 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
|
||||
// If path is outside extensions directory, prompt for confirmation
|
||||
if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName), &confirm)
|
||||
if !confirm {
|
||||
// Skip the forceRemoveAll but continue with the rest
|
||||
delete(loadedManifests, manifestName)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -30,12 +30,12 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
func DownloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
@@ -189,8 +189,7 @@ func HandleDownloadResponse(download *sliverpb.Download, cmd *cobra.Command, arg
|
||||
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
overwrite := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite local file?"}
|
||||
survey.AskOne(prompt, &overwrite, nil)
|
||||
_ = forms.Confirm("Overwrite local file?", &overwrite)
|
||||
if !overwrite {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3,6 +3,7 @@ package generate
|
||||
import (
|
||||
"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"
|
||||
@@ -142,12 +143,13 @@ 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)
|
||||
})
|
||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||
@@ -308,7 +310,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)")
|
||||
|
||||
@@ -341,7 +343,45 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
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")
|
||||
|
||||
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 and darwin/arm64)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
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 and macOS 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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -354,9 +394,11 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*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)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
registerImplantTargetFlagCompletions(cmd, con)
|
||||
}
|
||||
|
||||
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -17,6 +21,22 @@ 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) {
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateBeaconForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Generate beacon form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
if err := applyGenerateBeaconForm(cmd, result); err != nil {
|
||||
con.PrintErrorf("Failed to apply generate beacon form values: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
name, config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
@@ -67,3 +87,99 @@ func parseBeaconFlags(cmd *cobra.Command, config *clientpb.ImplantConfig) error
|
||||
config.BeaconJitter = int64(time.Duration(beaconJitter) * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldRunGenerateBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
|
||||
if con == nil || con.IsCLI {
|
||||
return false
|
||||
}
|
||||
if len(args) != 0 {
|
||||
return false
|
||||
}
|
||||
return cmd.Flags().NFlag() == 0
|
||||
}
|
||||
|
||||
func applyGenerateBeaconForm(cmd *cobra.Command, result *forms.GenerateBeaconFormResult) error {
|
||||
if err := cmd.Flags().Set("os", result.OS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Flags().Set("format", result.Format); err != nil {
|
||||
return err
|
||||
}
|
||||
name := strings.TrimSpace(result.Name)
|
||||
if name != "" {
|
||||
if err := cmd.Flags().Set("name", name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
save := strings.TrimSpace(result.Save)
|
||||
if save != "" {
|
||||
if err := cmd.Flags().Set("save", save); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c2Value := strings.TrimSpace(result.C2Value)
|
||||
switch result.C2Type {
|
||||
case "mtls":
|
||||
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "wg":
|
||||
if err := cmd.Flags().Set("wg", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "http":
|
||||
if err := cmd.Flags().Set("http", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "dns":
|
||||
if err := cmd.Flags().Set("dns", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "named-pipe":
|
||||
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case "tcp-pivot":
|
||||
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("unsupported C2 transport selection")
|
||||
}
|
||||
|
||||
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setOptionalInt64Flag(cmd *cobra.Command, name, value string) error {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := strconv.ParseInt(trimmed, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parsed < 0 {
|
||||
return fmt.Errorf("%s must be 0 or greater", name)
|
||||
}
|
||||
return cmd.Flags().Set(name, strconv.FormatInt(parsed, 10))
|
||||
}
|
||||
@@ -28,13 +28,14 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
@@ -85,10 +86,27 @@ var (
|
||||
|
||||
ErrNoExternalBuilder = errors.New("no external builders are available")
|
||||
ErrNoValidBuilders = errors.New("no valid external builders for target")
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateForm(cmd, con, args) {
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Generate form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
if err := applyGenerateForm(cmd, result); err != nil {
|
||||
con.PrintErrorf("Failed to apply generate form values: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
name, config := parseCompileFlags(cmd, con)
|
||||
if config == nil {
|
||||
return
|
||||
@@ -102,14 +120,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
switch err {
|
||||
case ErrNoExternalBuilder:
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
case ErrNoValidBuilders:
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
default:
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
@@ -117,6 +136,58 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRunGenerateForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
|
||||
if con == nil || con.IsCLI {
|
||||
return false
|
||||
}
|
||||
if len(args) != 0 {
|
||||
return false
|
||||
}
|
||||
return cmd.Flags().NFlag() == 0
|
||||
}
|
||||
|
||||
func applyGenerateForm(cmd *cobra.Command, result *forms.GenerateFormResult) error {
|
||||
if err := cmd.Flags().Set("os", result.OS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.Flags().Set("format", result.Format); err != nil {
|
||||
return err
|
||||
}
|
||||
name := strings.TrimSpace(result.Name)
|
||||
if name != "" {
|
||||
if err := cmd.Flags().Set("name", name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
save := strings.TrimSpace(result.Save)
|
||||
if save != "" {
|
||||
if err := cmd.Flags().Set("save", save); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c2Value := strings.TrimSpace(result.C2Value)
|
||||
switch result.C2Type {
|
||||
case "mtls":
|
||||
return cmd.Flags().Set("mtls", c2Value)
|
||||
case "wg":
|
||||
return cmd.Flags().Set("wg", c2Value)
|
||||
case "http":
|
||||
return cmd.Flags().Set("http", c2Value)
|
||||
case "dns":
|
||||
return cmd.Flags().Set("dns", c2Value)
|
||||
case "named-pipe":
|
||||
return cmd.Flags().Set("named-pipe", c2Value)
|
||||
case "tcp-pivot":
|
||||
return cmd.Flags().Set("tcp-pivot", c2Value)
|
||||
default:
|
||||
return errors.New("unsupported C2 transport selection")
|
||||
}
|
||||
}
|
||||
|
||||
func expandPath(path string) string {
|
||||
// unless path starts with ~
|
||||
if len(path) == 0 || path[0] != 126 {
|
||||
@@ -134,13 +205,10 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
|
||||
save = expandPath(save)
|
||||
fi, err := os.Stat(save)
|
||||
if os.IsNotExist(err) {
|
||||
con.Printf("%s does not exist\n", save)
|
||||
if strings.HasSuffix(save, "/") {
|
||||
con.Printf("%s is dir\n", save)
|
||||
os.MkdirAll(save, 0o700)
|
||||
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
|
||||
} else {
|
||||
con.Printf("%s is not dir\n", save)
|
||||
saveDir := filepath.Dir(save)
|
||||
_, err := os.Stat(saveTo)
|
||||
if os.IsNotExist(err) {
|
||||
@@ -152,10 +220,8 @@ func saveLocation(save, DefaultName string, con *console.SliverClient) (string,
|
||||
if fi.IsDir() {
|
||||
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
|
||||
} else {
|
||||
con.PrintInfof("%s is not dir\n", save)
|
||||
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Overwrite existing file?", &confirm)
|
||||
if !confirm {
|
||||
return "", errors.New("file already exists")
|
||||
}
|
||||
@@ -288,7 +354,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
sgnEnabled := false
|
||||
|
||||
var exports []string
|
||||
exportsArg, _ := cmd.Flags().GetString("exports")
|
||||
@@ -312,8 +377,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
sgnEnabled, _ = cmd.Flags().GetBool("disable-sgn")
|
||||
sgnEnabled = !sgnEnabled
|
||||
case "service":
|
||||
configFormat = clientpb.OutputFormat_SERVICE
|
||||
isService = true
|
||||
@@ -330,9 +393,15 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
if targetOS == "" || targetArch == "" {
|
||||
return "", nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
|
||||
return "", nil
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
||||
if targetOS != "windows" && targetOS != "darwin" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows and macOS\n")
|
||||
return "", nil
|
||||
}
|
||||
if targetOS == "darwin" && targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
@@ -344,6 +413,19 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
return "", nil
|
||||
}
|
||||
|
||||
shellcodeConfig, err := parseShellcodeFlags(cmd, targetOS, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
shellcodeEncoder, err := parseShellcodeEncoderFlag(cmd, targetArch, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
sgnEnabled := shellcodeEncoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI // legacy field
|
||||
|
||||
var tunIP net.IP
|
||||
if wg, _ := cmd.Flags().GetString("wg"); wg != "" {
|
||||
uniqueWGIP, err := con.Rpc.GenerateUniqueIP(context.Background(), &commonpb.Empty{})
|
||||
@@ -381,6 +463,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
Debug: debug,
|
||||
Evasion: evasion,
|
||||
SGNEnabled: sgnEnabled,
|
||||
ShellcodeEncoder: shellcodeEncoder,
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
@@ -415,11 +498,264 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
|
||||
DebugFile: debugFile,
|
||||
HTTPC2ConfigName: c2Profile,
|
||||
ShellcodeConfig: shellcodeConfig,
|
||||
}
|
||||
|
||||
return name, config
|
||||
}
|
||||
|
||||
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
|
||||
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
if !cmd.Flags().Changed("shellcode-entropy") {
|
||||
shellcodeEntropy, _ = cmd.Flags().GetUint32("donut-entropy")
|
||||
}
|
||||
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
if !cmd.Flags().Changed("shellcode-compress") {
|
||||
shellcodeCompressEnabled, _ = cmd.Flags().GetBool("donut-compress")
|
||||
}
|
||||
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
if !cmd.Flags().Changed("shellcode-exitopt") {
|
||||
shellcodeExitOpt, _ = cmd.Flags().GetUint32("donut-exitopt")
|
||||
}
|
||||
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
if !cmd.Flags().Changed("shellcode-bypass") {
|
||||
shellcodeBypass, _ = cmd.Flags().GetUint32("donut-bypass")
|
||||
}
|
||||
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
if !cmd.Flags().Changed("shellcode-headers") {
|
||||
shellcodeHeaders, _ = cmd.Flags().GetUint32("donut-headers")
|
||||
}
|
||||
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
if !cmd.Flags().Changed("shellcode-thread") {
|
||||
shellcodeThread, _ = cmd.Flags().GetBool("donut-thread")
|
||||
}
|
||||
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
if !cmd.Flags().Changed("shellcode-unicode") {
|
||||
shellcodeUnicode, _ = cmd.Flags().GetBool("donut-unicode")
|
||||
}
|
||||
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
if !cmd.Flags().Changed("shellcode-oep") {
|
||||
shellcodeOEP, _ = cmd.Flags().GetUint32("donut-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") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-compress") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
cmd.Flags().Changed("donut-headers") ||
|
||||
cmd.Flags().Changed("donut-thread") ||
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Shellcode options only apply when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
windowsOnlyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
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") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
cmd.Flags().Changed("donut-headers") ||
|
||||
cmd.Flags().Changed("donut-thread") ||
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
// macOS shellcode currently only supports compression.
|
||||
if targetOS != "windows" {
|
||||
if targetOS == "darwin" {
|
||||
if windowsOnlyChanged {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
|
||||
}
|
||||
} else if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows and macOS shellcode, ignoring.\n")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Compress: shellcodeCompress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, 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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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, configFormat clientpb.OutputFormat, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder != "" {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// parseTrafficEncoderArgs - parses the traffic encoder args and returns a bool indicating if traffic encoders are enabled.
|
||||
func parseTrafficEncoderArgs(cmd *cobra.Command, httpC2Enabled bool, con *console.SliverClient) (bool, []*commonpb.File) {
|
||||
trafficEncoders, _ := cmd.Flags().GetString("traffic-encoders")
|
||||
@@ -466,9 +802,8 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
|
||||
console.Bold, target, console.Normal,
|
||||
)
|
||||
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
||||
prompt := &survey.Confirm{Message: "Attempt to build generic implant?"}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
|
||||
if !confirm {
|
||||
return "", ""
|
||||
}
|
||||
@@ -576,7 +911,7 @@ func hasValidC2AdvancedOptions(options url.Values) (bool, error) {
|
||||
if testValue != "wininet" {
|
||||
return false, fmt.Errorf("C2 option \"driver\" must be empty for the default driver or \"wininet\" for the wininet driver (Windows only)")
|
||||
}
|
||||
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds", "force-base32":
|
||||
case "force-http", "disable-accept-header", "disable-upgrade-header", "ask-proxy-creds":
|
||||
if testValue != "true" && testValue != "false" {
|
||||
return false, fmt.Errorf("C2 option \"%s\" must be a boolean value: true or false", key)
|
||||
}
|
||||
@@ -738,11 +1073,8 @@ func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(uri.Path, "/pipe/") {
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri),
|
||||
}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Named pipe '%s' is missing the 'pipe' path prefix\nContinue anyways?", uri), &confirm)
|
||||
if !confirm {
|
||||
return nil, fmt.Errorf("invalid namedpipe path: %s", uri.Path)
|
||||
}
|
||||
@@ -902,12 +1234,58 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && 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, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0o700)
|
||||
err = os.WriteFile(saveTo, fileData, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -954,22 +1332,49 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
if !config.SGNEnabled {
|
||||
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && 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 == "" {
|
||||
con.PrintWarnf("Unknown shellcode encoder enum %d, skipping.\n", int32(encoder))
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
con.PrintErrorf("%s\n", msg)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1049,8 +1454,7 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
|
||||
con.PrintWarnf("For more information please read %s\n", crossCompilerInfoURL)
|
||||
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Try to compile anyways (will likely fail)?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
_ = forms.Confirm("Try to compile anyways (will likely fail)?", &confirm)
|
||||
return confirm
|
||||
}
|
||||
|
||||
@@ -1086,11 +1490,12 @@ func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient
|
||||
choices = append(choices, builder.Name)
|
||||
}
|
||||
choice := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an external builder:",
|
||||
Options: choices,
|
||||
_ = forms.Select("Select an external builder:", choices, &choice)
|
||||
|
||||
if choice == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
survey.AskOne(prompt, &choice, nil)
|
||||
|
||||
for _, builder := range builders {
|
||||
if builder.Name == choice {
|
||||
return builder, nil
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -24,8 +24,7 @@ func ImplantsRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' build?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' build?", name), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ package generate
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
@@ -42,12 +42,8 @@ func ImplantsStageCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
options = append(options, name)
|
||||
}
|
||||
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "Select sessions and beacons to expose:",
|
||||
Options: options,
|
||||
}
|
||||
selected := []string{}
|
||||
survey.AskOne(prompt, &selected)
|
||||
_ = forms.MultiSelect("Select sessions and beacons to expose:", options, &selected)
|
||||
|
||||
_, err = con.Rpc.StageImplantBuild(context.Background(), &clientpb.ImplantStageReq{Build: selected})
|
||||
if err != nil {
|
||||
|
||||
@@ -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,9 +46,40 @@ 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)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,8 +19,11 @@ package generate
|
||||
*/
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -28,7 +31,22 @@ import (
|
||||
// ProfilesNewCmd - Create a new implant profile.
|
||||
func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var name string
|
||||
if len(args) > 0 {
|
||||
if shouldRunGenerateProfilesNewForm(cmd, con, args) {
|
||||
result, err := forms.GenerateProfilesNewForm()
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Profiles new form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
profileName, err := applyGenerateProfilesNewForm(cmd, result)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to apply profiles new form values: %s\n", err)
|
||||
return
|
||||
}
|
||||
name = profileName
|
||||
} else if len(args) > 0 {
|
||||
name = args[0]
|
||||
}
|
||||
// name := ctx.Args.String("name")
|
||||
@@ -51,7 +69,22 @@ func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
// ProfilesNewBeaconCmd - Create a new beacon profile.
|
||||
func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var name string
|
||||
if len(args) > 0 {
|
||||
if shouldRunGenerateProfilesNewBeaconForm(cmd, con, args) {
|
||||
result, err := forms.GenerateProfilesNewBeaconForm()
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
}
|
||||
con.PrintErrorf("Profiles new beacon form failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
profileName, err := applyGenerateProfilesNewBeaconForm(cmd, result)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to apply profiles new beacon form values: %s\n", err)
|
||||
return
|
||||
}
|
||||
name = profileName
|
||||
} else if len(args) > 0 {
|
||||
name = args[0]
|
||||
}
|
||||
// name := ctx.Args.String("name")
|
||||
@@ -80,3 +113,151 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRunGenerateProfilesNewForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
|
||||
if con == nil || con.IsCLI {
|
||||
return false
|
||||
}
|
||||
if len(args) != 0 {
|
||||
return false
|
||||
}
|
||||
return cmd.Flags().NFlag() == 0
|
||||
}
|
||||
|
||||
func applyGenerateProfilesNewForm(cmd *cobra.Command, result *forms.GenerateProfilesNewFormResult) (string, error) {
|
||||
if err := cmd.Flags().Set("os", result.OS); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := cmd.Flags().Set("format", result.Format); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
profileName := strings.TrimSpace(result.ProfileName)
|
||||
if profileName == "" {
|
||||
return "", errors.New("profile name required")
|
||||
}
|
||||
|
||||
implantName := strings.TrimSpace(result.Name)
|
||||
if implantName != "" {
|
||||
if err := cmd.Flags().Set("name", implantName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
c2Value := strings.TrimSpace(result.C2Value)
|
||||
switch result.C2Type {
|
||||
case "mtls":
|
||||
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "wg":
|
||||
if err := cmd.Flags().Set("wg", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "http":
|
||||
if err := cmd.Flags().Set("http", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "dns":
|
||||
if err := cmd.Flags().Set("dns", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "named-pipe":
|
||||
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "tcp-pivot":
|
||||
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
default:
|
||||
return "", errors.New("unsupported C2 transport selection")
|
||||
}
|
||||
|
||||
return profileName, nil
|
||||
}
|
||||
|
||||
func shouldRunGenerateProfilesNewBeaconForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
|
||||
if con == nil || con.IsCLI {
|
||||
return false
|
||||
}
|
||||
if len(args) != 0 {
|
||||
return false
|
||||
}
|
||||
return cmd.Flags().NFlag() == 0
|
||||
}
|
||||
|
||||
func applyGenerateProfilesNewBeaconForm(cmd *cobra.Command, result *forms.GenerateProfilesNewBeaconFormResult) (string, error) {
|
||||
if err := cmd.Flags().Set("os", result.OS); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := cmd.Flags().Set("arch", result.Arch); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := cmd.Flags().Set("format", result.Format); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
profileName := strings.TrimSpace(result.ProfileName)
|
||||
if profileName == "" {
|
||||
return "", errors.New("profile name required")
|
||||
}
|
||||
|
||||
implantName := strings.TrimSpace(result.Name)
|
||||
if implantName != "" {
|
||||
if err := cmd.Flags().Set("name", implantName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
c2Value := strings.TrimSpace(result.C2Value)
|
||||
switch result.C2Type {
|
||||
case "mtls":
|
||||
if err := cmd.Flags().Set("mtls", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "wg":
|
||||
if err := cmd.Flags().Set("wg", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "http":
|
||||
if err := cmd.Flags().Set("http", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "dns":
|
||||
if err := cmd.Flags().Set("dns", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "named-pipe":
|
||||
if err := cmd.Flags().Set("named-pipe", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "tcp-pivot":
|
||||
if err := cmd.Flags().Set("tcp-pivot", c2Value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
default:
|
||||
return "", errors.New("unsupported C2 transport selection")
|
||||
}
|
||||
|
||||
if err := setOptionalInt64Flag(cmd, "days", result.Days); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "hours", result.Hours); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "minutes", result.Minutes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "seconds", result.Seconds); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := setOptionalInt64Flag(cmd, "jitter", result.Jitter); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return profileName, nil
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -45,8 +45,7 @@ func ProfilesRmCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' profile?", name)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Remove '%s' profile?", name), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -26,10 +26,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
@@ -140,10 +140,7 @@ func TrafficEncodersAddCmd(cmd *cobra.Command, con *console.SliverClient, args [
|
||||
// saveFailedSample - Save the sample the encoder failed to properly encode/decode.
|
||||
func saveFailedSample(encoderName string, test *clientpb.TrafficEncoderTest) {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
_ = forms.Confirm(fmt.Sprintf("Failed to add traffic encoder %s, save failed sample to disk?", encoderName), &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -286,10 +283,10 @@ func SelectTrafficEncoder(con *console.SliverClient) string {
|
||||
}
|
||||
sort.Strings(encoderNames)
|
||||
var selectedEncoder string
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a traffic encoder:",
|
||||
Options: encoderNames,
|
||||
err = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return ""
|
||||
}
|
||||
survey.AskOne(prompt, &selectedEncoder)
|
||||
return selectedEncoder
|
||||
}
|
||||
@@ -47,37 +47,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 +132,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 +189,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),
|
||||
and Linux ELF. 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 +206,19 @@ 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 and macOS)
|
||||
--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)
|
||||
|
||||
|
||||
[[.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 +324,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]]
|
||||
@@ -368,6 +441,42 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
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>.`
|
||||
|
||||
@@ -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,713 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/util"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"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 (
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
|
||||
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
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.KeyMsg:
|
||||
if m.clearMessage {
|
||||
m.message = ""
|
||||
m.clearMessage = false
|
||||
}
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
m.action = actionQuit
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch m.mode {
|
||||
case modeCommand:
|
||||
return m.handleCommand(msg)
|
||||
case modeHexEdit:
|
||||
return m.handleHexEdit(msg)
|
||||
case modeASCIIEdit:
|
||||
return m.handleASCIIEdit(msg)
|
||||
default:
|
||||
return m.handleNormal(msg)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) View() string {
|
||||
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 b.String()
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
||||
|
||||
if m.height < 3 {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
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(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
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)
|
||||
case tea.KeyRunes:
|
||||
m.command += string(msg.Runes)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
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
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
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(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
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()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
if r < 0 || r > 0xFF {
|
||||
continue
|
||||
}
|
||||
m.setByte(byte(r))
|
||||
m.moveRight()
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
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()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
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 {
|
||||
if isModified {
|
||||
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
|
||||
} else {
|
||||
hexCells[i] = cursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = cursorStyle.Render(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,349 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
"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)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
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
|
||||
}
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -86,12 +86,8 @@ func SelectHostIOC(host *clientpb.Host, con *console.SliverClient) (*clientpb.IO
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an IOC:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
_ = forms.Select("Select an IOC:", options, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
@@ -172,12 +172,8 @@ func SelectHost(con *console.SliverClient) (*clientpb.Host, error) {
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a host:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
_ = forms.Select("Select a host:", options, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package info
|
||||
|
||||
import (
|
||||
"context"
|
||||
insecureRand "math/rand"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ func PingCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
nonce := insecureRand.Intn(999999)
|
||||
nonce := util.Intn(999999)
|
||||
con.PrintInfof("Ping %d\n", nonce)
|
||||
pong, err := con.Rpc.Ping(context.Background(), &sliverpb.Ping{
|
||||
Nonce: int32(nonce),
|
||||
|
||||
@@ -82,7 +82,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
|
||||
@@ -163,5 +162,5 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
|
||||
})
|
||||
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, httpCmd, httpsCmd, stageCmd}
|
||||
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -39,7 +39,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
confirm := false
|
||||
con.PrintWarnf("WARNING: This will kill the remote implant process\n\n")
|
||||
if session != nil {
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active session?"}, &confirm, nil)
|
||||
_ = forms.Confirm("Kill the active session?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func KillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.ActiveTarget.Background()
|
||||
return
|
||||
} else if beacon != nil {
|
||||
survey.AskOne(&survey.Confirm{Message: "Kill the active beacon?"}, &confirm, nil)
|
||||
_ = forms.Confirm("Kill the active beacon?", &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
@@ -106,11 +106,7 @@ func SelectLoot(cmd *cobra.Command, rpc rpcpb.SliverRPCClient) (*clientpb.Loot,
|
||||
}
|
||||
|
||||
selected := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a piece of loot:",
|
||||
Options: options,
|
||||
}
|
||||
err = survey.AskOne(prompt, &selected)
|
||||
err = forms.Select("Select a piece of loot:", options, &selected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Loaded 100 of 6862 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user