Compare commits

...
189 Commits
Author SHA1 Message Date
moloch-- a7b5dbe6ee Tweak colors for user blocks 2026-03-20 20:29:28 -07:00
moloch-- c188dc1e69 Make messages copy-able 2026-03-20 20:19:02 -07:00
moloch-- 9fc9d1762e Merge branch 'master' into ai/tui2 2026-03-20 20:00:05 -07:00
Joe 4ef86448d0 Merge pull request #2224 from BishopFox/clean-makefile
Cleanup makefile vars
2026-03-20 08:22:50 -07:00
moloch-- 02117a6072 Cleanup makefile vars 2026-03-20 07:51:48 -07:00
moloch-- c272c8abac Update mcp dep 2026-03-19 09:26:17 -07:00
moloch-- cdf2412b39 Update mcp dep 2026-03-19 09:25:56 -07:00
moloch-- 40f52791d3 Update gprc dep 2026-03-19 08:55:25 -07:00
moloch-- a5d8bd16b9 Merge branch 'master' of github.com:BishopFox/sliver 2026-03-19 08:53:40 -07:00
moloch-- bf113f2151 npm update 2026-03-19 08:53:29 -07:00
moloch-- 4cd010afb9 wip on copy/paste 2026-03-19 08:52:46 -07:00
Joe 04b0bdd199 Merge pull request #2222 from skoveit/fix/alias-save-beacon-hostname
fix: populate hostname from beacon when using --save flag
2026-03-16 19:05:11 -07:00
Joe 9682db93b3 Merge branch 'master' into fix/alias-save-beacon-hostname 2026-03-16 18:47:06 -07:00
Joe 1d7ffb4ac2 Merge pull request #2221 from BishopFox/ai/tui
Ai/tui
2026-03-16 18:46:53 -07:00
0xOral c5e7cd6354 fix: use beacon.GetHostname() instead of direct field access 2026-03-17 03:16:19 +02:00
0xOral b20ba7284b fix: use beacon hostname when saving output with --save flag 2026-03-17 02:59:00 +02:00
moloch-- 49498210f8 Fix WireGuard generate test IP collisions 2026-03-16 17:20:33 -07:00
moloch-- 14e9121302 Go 1.25.8 2026-03-15 20:25:02 -07:00
moloch-- 8bd0257dd9 Go 1.25.8 2026-03-15 20:14:18 -07:00
Joe 93fdfb3add Merge branch 'master' into ai/tui 2026-03-15 20:03:27 -07:00
moloch-- e02b03cc70 Go 1.25.7 2026-03-15 20:03:01 -07:00
Joe 6831fe50a6 Merge pull request #2220 from skoveit/feat/multi-protocol-beacons
feat: multi-protocol implant builds via TUI
2026-03-15 20:02:36 -07:00
moloch-- 405108866d Update gh action workflows to 1.26.1 2026-03-15 15:34:34 -07:00
moloch-- 4d551b71e7 Fix huh form rendering 2026-03-15 14:11:33 -07:00
moloch-- a7edd9838d Sync prompt to stdout 2026-03-15 13:50:04 -07:00
moloch-- 9ef54ca6b1 Add mouse support to ai tui 2026-03-15 12:48:43 -07:00
moloch-- 4622c50288 Add ai chat sync 2026-03-15 12:26:03 -07:00
moloch-- b1ee0355af isolate multiplayer wg from c2 wg 2026-03-15 11:51:09 -07:00
moloch-- 01d9b213f2 fix grpc/mtls+wg for multiplayer 2026-03-15 11:11:27 -07:00
moloch-- f56886bce5 Merge grpc/mtls+wg branch 2026-03-15 10:55:50 -07:00
moloch-- 88176dfd00 Improve docs command 2026-03-15 10:31:41 -07:00
moloch-- 45dc3ea09b Improve doc file selector 2026-03-15 10:25:38 -07:00
moloch-- 682b3c2409 Upgrade to charm v2 libs 2026-03-15 09:47:29 -07:00
moloch-- 3a93f312b4 Add optional Wireguard multiplayer 2026-03-15 09:38:54 -07:00
moloch-- 2ca95f7a7c Add docs command 2026-03-15 09:21:24 -07:00
moloch-- d2319432ab Create docs go pkg 2026-03-15 08:53:52 -07:00
moloch-- 253f1a8806 Create docs go pkg 2026-03-15 08:52:05 -07:00
moloch-- 2c025d5970 Add new conversation modal 2026-03-15 07:56:11 -07:00
moloch-- f0034683f9 Tweak tui borders 2026-03-15 07:16:45 -07:00
moloch-- 8e8398b654 Tweak border 2026-03-14 23:20:07 -07:00
moloch-- aa02293f05 Fix borders 2026-03-14 22:34:58 -07:00
moloch-- f967a32308 Add scrollbar 2026-03-14 22:22:46 -07:00
moloch-- 99ec812455 Improve scroll of conversation pane, move context info to modal 2026-03-14 22:08:28 -07:00
moloch-- eeef542eeb Make conversation scrollable 2026-03-14 21:48:54 -07:00
moloch-- 6d7a10b392 Switch to fantasy to call models 2026-03-14 21:22:01 -07:00
Joe ca40c0a1e7 Merge pull request #2219 from BishopFox/fix/windows-ci
Fix Windows CI
2026-03-14 20:48:12 -07:00
moloch-- 4a5ad6ea92 Redesign delete modal 2026-03-14 20:13:02 -07:00
moloch-- 266dabf6c3 Redesign modals 2026-03-14 20:03:49 -07:00
moloch-- a4c8968539 Resize tui on window resize 2026-03-14 18:54:09 -07:00
moloch-- 613c030ea3 Add markdown rendering 2026-03-14 15:38:54 -07:00
moloch-- 9f6bfb6714 Working animation 2026-03-14 15:27:35 -07:00
0xOral a764814a8e ux: refine reconfig command warnings and safety prompts 2026-03-14 23:16:30 +02:00
0xOral 0c9ce138a0 allow runtime protocol switching 2026-03-14 22:54:25 +02:00
0xOral 6c1dcf6a06 add flags to include multiple transport protocols at build time 2026-03-14 22:53:33 +02:00
moloch-- fffe93819d Improve server-side ai grpc handlers 2026-03-14 13:31:32 -07:00
moloch-- 635ee421e3 Add animation 2026-03-14 12:54:20 -07:00
moloch-- 3ce6d69474 Add server-side ai-config form 2026-03-14 12:00:28 -07:00
moloch-- cd523c7f77 Wire tui to ai grpc 2026-03-14 11:43:21 -07:00
moloch-- 1f1b759914 Merge branch 'codex/add-bubbletea-ai-tui-layout' into ai/tui 2026-03-14 11:15:14 -07:00
moloch-- b78915fcbd Basic db layout 2026-03-14 11:14:54 -07:00
moloch-- fa0e13a5cc Basic tui layout 2026-03-14 11:14:03 -07:00
moloch-- 924bcddfd2 Fix gogo test on Windows 2026-03-14 10:25:34 -07:00
moloch-- affbf61452 Fix unit test on windows 2026-03-14 09:57:52 -07:00
moloch-- 7c6b2f70e4 Add windows ci runner 2026-03-14 08:42:57 -07:00
moloch-- c46f2938de Fix cross-platform issues with running ci on windows 2026-03-14 08:37:31 -07:00
moloch-- b355fe70d6 Add Windows makefile 2026-03-13 16:14:11 -07:00
moloch-- 10b1079286 Tweak agents.md 2026-03-13 15:29:45 -07:00
Joe 9c042e6977 Merge pull request #2217 from BishopFox/fix/cert-revoke
Improve operator cert revocation
2026-03-13 12:04:26 -07:00
Joe 62583cac8f Merge branch 'master' into fix/cert-revoke 2026-03-13 11:02:34 -07:00
moloch 95764945b8 Improve operator cert revocation 2026-03-13 10:59:00 -07:00
Joe 26abda1c15 Merge pull request #2216 from skoveit/fix/timeout-int64p-mismatch
fix: timeout flag ignored in aliases and extensions due to type mismatch
2026-03-13 07:38:21 -07:00
moloch 01dd3cac72 Go mod tidy 2026-03-13 07:02:06 -07:00
0xOral f8faa1f767 fix: normalize all command timeout flags to Int64P across client codebase 2026-03-13 02:49:01 +02:00
0xOral a9e48e84cd fix: extension timeout flag ignored due to IntP/Int64P mismatch 2026-03-13 02:30:55 +02:00
0xOral 9d6704d925 fix: alias -t flag always capped at 30s due to IntP/Int64P type mismatch (#2183) 2026-03-13 02:04:35 +02:00
Joe ce5157e2e9 Merge pull request #2213 from skoveit/fix/wininet-handle-leak
fix(wininet): Fix handle leak in WinINet driver
2026-03-11 08:29:42 -07:00
0xOral 7d19051df8 fix(wininet): close connection and request handles after each request 2026-03-11 01:32:27 +02:00
Joe a0d3bd579f Merge pull request #2211 from skoveit/feat/configurable-portfwd-keepalive
feat: make TCP keepalive period configurable for portfwd and rportfwd
2026-03-08 10:36:42 -07:00
0xOral 82c309632a default keepalive value 30s for both rportfwd and portfwd 2026-03-08 17:27:27 +02:00
0xOral 5c0b8ec460 disable keepalive with --keepalive -1 value 2026-03-08 17:25:06 +02:00
0xOral 7791a86eb4 configurable keepalive: portfwd --keepalive 2026-03-08 17:20:35 +02:00
Joe 7ac4db3fa0 Merge pull request #2207 from BishopFox/fix/beacon-getsystem
Fix for getsystem in beacon mode
2026-03-05 18:31:39 -07:00
moloch-- 512bc822ef Implement fix for getsystem in beacon mode 2026-03-05 08:22:42 -08:00
Joe f2bb9c3733 Merge pull request #2205 from BishopFox/feature/lazy-lg-msg
Lazy spool large messages to disk
2026-03-04 15:40:56 -07:00
moloch-- 0bd4f100fd Lazy spool large messages to disk 2026-03-04 14:21:07 -08:00
Joe 3230322fd6 Merge pull request #2201 from BishopFox/feature/exec-shellcode
Add support for wasm-donut options in windows `execute-shellcode`
2026-03-02 08:23:49 -07:00
moloch-- 82d979436b Add support for additional wasm-donute options in windows execute-shellcode 2026-03-01 22:35:37 -08:00
Joe d2632e75bd Merge pull request #2199 from BishopFox/feature/go-tests2
Refactor unit tests to not write to main db
2026-03-01 09:47:23 -07:00
moloch-- a55831fb44 Go v1.25.7 2026-02-28 22:39:46 -08:00
moloch-- 39d4edec43 Tweak go-tests.sh 2026-02-28 22:08:59 -08:00
moloch-- bbdb08e7a4 Refactor go-tests.sh 2026-02-28 14:12:37 -08:00
moloch-- 39b488bd25 Refactor unit tests to not write to main db 2026-02-28 13:11:14 -08:00
Joe e7b8a24534 Merge pull request #2198 from BishopFox/feature/handler-recover
Feature/handler recover
2026-02-28 11:39:00 -07:00
moloch-- 17b9d611f8 Add panic/recover to mtls, dns, and wg handlers 2026-02-28 09:56:48 -08:00
moloch-- ed9075d065 Add better nil checks to various pb handlers 2026-02-28 09:52:40 -08:00
Joe 1f7221933d Merge pull request #2194 from BishopFox/crush/v2
Upgrade crush libs to v2
2026-02-25 09:22:24 -07:00
moloch-- 6333e0691a Upgrade crush libs to v2 2026-02-24 17:40:13 -08:00
moloch-- 3bbaf80510 v1.7.3 2026-02-23 20:49:28 -08:00
moloch-- 25726d8105 Revert wasm-donut to v0.0.3 2026-02-23 20:48:53 -08:00
moloch-- 67e9a7f37f Update wasm-donut to v0.0.6 2026-02-23 19:33:26 -08:00
moloch-- a5b6935da6 Tweak AGENTS.md 2026-02-23 07:10:28 -08:00
moloch-- 2e36f45bdf v1.7.2 2026-02-22 17:33:08 -08:00
Joe 3e06a9d5ee Merge pull request #2191 from BishopFox/feature/file-tab-completer
Feature/file tab completer
2026-02-22 18:32:27 -07:00
moloch-- 952b2df49d Update beignet 2026-02-22 16:42:32 -08:00
moloch-- 9769f1bb57 Fix mod vendor 2026-02-22 16:41:16 -08:00
moloch-- f5f2c80ad3 Update malasada to v0.0.3 2026-02-22 16:38:41 -08:00
moloch-- bb2edf63cf Add file tab completer to more commands 2026-02-22 16:37:30 -08:00
moloch-- cd711afe8a Add tab completer to generate --save 2026-02-22 16:26:32 -08:00
Joe f9bbbb34bb Merge pull request #2190 from BishopFox/skoveit-feature/PE-Metadata-Spoofing
Skoveit feature/pe metadata spoofing
2026-02-21 23:29:33 -07:00
moloch-- 64b91c56c0 Fix unit tests 2026-02-21 15:14:26 -08:00
moloch-- a6c7867a26 Fix unit tests 2026-02-21 14:22:18 -08:00
moloch-- 7f25c3bb06 Expand pe metadata spoofing feature 2026-02-21 12:36:47 -08:00
moloch-- 153cf29551 Fix unit tests 2026-02-21 06:30:01 -08:00
jjensn b85161e797 Declare vars before use 2026-02-21 06:29:59 -08:00
jjensn ed906fcffe typo: Add yamux client constructor 2026-02-21 06:29:58 -08:00
jjensn 3bbcf5697a Yamux logging for debug implants 2026-02-21 06:29:56 -08:00
moloch-- 4a425d2fd6 Fix 'websites rm' add tab completer issue #2185 2026-02-21 06:29:55 -08:00
Adam d0f752e118 Fix execute infinite spin
Signed-off-by: Adam <[email protected]>
2026-02-21 06:29:54 -08:00
Adam 7167621577 Change env-inheritance flag default to false
Signed-off-by: Adam <[email protected]>
2026-02-21 06:29:47 -08:00
Joe c3582fc203 Merge pull request #2189 from BishopFox/jjensn-fix/yamux-disable-logging
Jjensn fix/yamux disable logging
2026-02-19 22:29:46 -07:00
moloch-- 07c95b203f Fix unit tests 2026-02-19 18:05:57 -08:00
moloch-- 958e44deef Fix 'websites rm' add tab completer issue #2185 2026-02-19 17:54:55 -08:00
Adam 836395a2d6 Fix execute infinite spin
Signed-off-by: Adam <[email protected]>
2026-02-19 17:54:53 -08:00
Adam 1a7a1c9b2d Change env-inheritance flag default to false
Signed-off-by: Adam <[email protected]>
2026-02-19 17:54:27 -08:00
Joe 4ba285796f Merge pull request #2188 from BishopFox/fix/websites-rm
Fix 'websites rm' add tab completer issue #2185
2026-02-19 18:51:41 -07:00
moloch-- a05dfad100 Fix 'websites rm' add tab completer issue #2185 2026-02-19 17:30:33 -08:00
Joe 0eb29ad9b8 Merge pull request #2187 from Aptimex/execute-fix
Fix: Execute default env inheritance issues
2026-02-19 18:06:43 -07:00
Adam ad8b329f85 Fix execute infinite spin
Signed-off-by: Adam <[email protected]>
2026-02-19 16:45:39 -07:00
Adam c6206257b6 Change env-inheritance flag default to false
Signed-off-by: Adam <[email protected]>
2026-02-19 16:20:59 -07:00
Ronan Kervella 8bbf06c156 Merge branch 'master' into feature/PE-Metadata-Spoofing 2026-02-19 12:42:31 -08:00
jjensn 4d4eab998f Declare vars before use 2026-02-18 13:34:24 -05:00
jjensn c081f17380 typo: Add yamux client constructor 2026-02-17 13:17:44 -05:00
jjensn fafbac3fa6 Yamux logging for debug implants 2026-02-17 12:59:34 -05:00
moloch-- d9c89c81cf Fix typo in docs 2026-02-16 13:14:40 -08:00
moloch-- 061221905b Add discord button to upper right 2026-02-16 13:09:03 -08:00
moloch-- d0600e2be7 Tweak docs site loading 2026-02-16 10:44:51 -08:00
moloch-- 26abbe3860 Update wasm-donut to v0.0.5 2026-02-16 09:58:57 -08:00
Anas 55a386ae1c Merge branch 'master' into feature/PE-Metadata-Spoofing 2026-02-16 19:54:15 +02:00
moloch-- 2bfdb218ff Update wasm-donut to v0.0.5 2026-02-16 09:44:22 -08:00
moloch-- 715b84e799 Update readme 2026-02-16 09:26:49 -08:00
moloch-- faea2ff639 Add link buttons to home page 2026-02-16 08:58:39 -08:00
moloch-- 1b253f8dec Add community talks section 2026-02-16 08:46:12 -08:00
moloch-- eb38724322 Add mtls and wg c2 docs 2026-02-16 08:35:01 -08:00
moloch-- 05dedf4473 Add mermaid chart component to md renderer 2026-02-16 08:06:36 -08:00
moloch-- 28c6b82a79 Update config file docs 2026-02-16 07:46:18 -08:00
Anas 1f553496e3 Merge branch 'master' into feature/PE-Metadata-Spoofing 2026-02-16 15:50:07 +02:00
0xOral cd5db464e0 fix protoc version 2026-02-16 15:25:21 +02:00
moloch-- 0bf0ac6c7b Update config file md 2026-02-15 21:52:57 -08:00
Joe 0cf5a47cfd Merge pull request #2181 from BishopFox/fix/gzip-bomb
Add limit to gzip size
2026-02-15 22:27:56 -07:00
moloch-- 8d6759924b Add limit to gzip size 2026-02-15 21:13:54 -08:00
Joe 8d5dbd2050 Merge pull request #2180 from BishopFox/feature/shell-mgmt
Feature/Shell Management
2026-02-15 22:12:08 -07:00
moloch-- 804e149113 Add shell kill option 2026-02-15 20:58:07 -08:00
moloch-- 9ced4cfd50 Add shell mgmt features 2026-02-15 20:51:18 -08:00
0xOral c8be5a5b1b add --spoof feature 2026-02-16 02:27:59 +02:00
moloch-- b7abf98c95 Tweak yt header 2026-02-15 07:30:51 -08:00
moloch-- 918ac1bb91 Tweak payload compatibility tables 2026-02-14 16:24:44 -08:00
moloch-- 1e57818312 Tweak cross-compile docs 2026-02-14 11:34:11 -08:00
moloch-- c44c14dd46 Tweak cross-compile docs 2026-02-14 11:33:48 -08:00
moloch-- 35f41cd62d Tweak compile from source instructions 2026-02-14 11:29:35 -08:00
moloch-- 4eff1cc053 Add getting started workshop 2026-02-14 11:22:12 -08:00
moloch-- 16981da431 upgrade docs to nextjs v16 2026-02-14 11:16:38 -08:00
moloch-- 3830c76292 npm update 2026-02-14 09:43:13 -08:00
moloch-- 73de93d6fb Fix content scrolling 2026-02-14 09:42:04 -08:00
moloch-- c7770c2f6e Add more yt links 2026-02-14 09:31:02 -08:00
moloch-- 21db908ce4 Hide footer on yt play 2026-02-14 09:23:11 -08:00
moloch-- b7b0247652 Add basic yt embeds 2026-02-14 08:52:15 -08:00
moloch-- 3dc7dc03d0 Fix moving sidebar 2026-02-14 08:40:26 -08:00
moloch-- 229452d7ed Fix moving sidebar 2026-02-14 08:39:23 -08:00
moloch-- d29ce9a08b Add payload compatibility table to docs 2026-02-14 08:19:18 -08:00
Joe 46f2b2b402 Merge pull request #2177 from BishopFox/skoveit-feature/dynamic-c2-endpoint
Skoveit feature/dynamic c2 endpoint
2026-02-13 11:50:36 -07:00
Joe 6ee2006387 Merge pull request #2170 from xtle0o0/fix/external-builder-authz-scope
Restrict external builder RPCs to assigned build jobs
2026-02-13 11:50:22 -07:00
moloch-- 2fd13ee2bc Regenerate pb 2026-02-13 07:22:06 -08:00
0xOral 2697568336 fix-export-ca-error-message 2026-02-13 07:21:54 -08:00
Joe 5013048b0b Merge pull request #2176 from skoveit/fix-export-ca-error-message
fix-export-ca-error-message
2026-02-13 08:13:46 -07:00
Joe 627b15572c Merge branch 'master' into feature/dynamic-c2-endpoint 2026-02-13 08:11:56 -07:00
0xOral 42c40c9b8d fix-export-ca-error-message 2026-02-12 19:45:52 +02:00
0xOral 405b282fed Dynamic C2 Endpoint Switching 2026-02-12 18:47:26 +02:00
Joe e32cd36e01 Merge pull request #2172 from skoveit/fix-off-by-one-naming
Fix: server panic on single-word lists and off-by-one in codename generation
2026-02-10 22:30:32 -07:00
Joe 492a2aeed6 Merge pull request #2171 from loresuso/loresuso/fix-zombie-shell
fix: wait system shell to cleanup zombie processes
2026-02-10 22:29:53 -07:00
Lorenzo Susini d6dafb0391 fix: wait system shell to cleanup zombie processes
Signed-off-by: Lorenzo Susini <[email protected]>
2026-02-10 18:01:39 +01:00
0xOral 49a1bcfd7d fix(codenames): fix off-by-one error and prevent panic on single-word lists 2026-02-10 17:05:13 +02:00
xtle0o0 a8d1bf6fb1 Restrict external builder RPCs to assigned builds 2026-02-10 12:00:24 +01:00
moloch-- 2f386b7e09 v1.7.1 2026-02-08 14:19:47 -08:00
Joe d7dce58397 Merge pull request #2168 from BishopFox/feature/mutli-console
Feature/mutli console
2026-02-08 07:47:05 -07:00
moloch-- 15114138a8 Save interactive settings to disk 2026-02-08 06:32:17 -08:00
moloch-- 2716090410 Add custom prompt configurations 2026-02-08 06:28:45 -08:00
moloch-- a805161245 Add themes and console switcher 2026-02-07 15:46:17 -08:00
Joe afd57891cd Merge pull request #2166 from BishopFox/linux/shellcode
Linux/shellcode
2026-02-07 14:32:19 -07:00
moloch--andCodex bdf4a6ed0d Codex changes
Co-authored-by: Codex
2026-02-07 12:54:03 -08:00
2108 changed files with 383684 additions and 156147 deletions

No files matched your search

+2 -2
View File
@@ -41,10 +41,10 @@ jobs:
with:
fetch-depth: 0
- name: Go 1.25
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.6"
go-version: "1.25.8"
- name: Install Minisign
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
+2 -2
View File
@@ -16,10 +16,10 @@ jobs:
steps:
# Setup go environment
- name: Go 1.25
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.6"
go-version: "1.25.8"
- name: Checkout repository
uses: actions/checkout@v6
+53 -8
View File
@@ -6,11 +6,13 @@ on:
paths:
- "go.mod"
- "go.sum"
- ".github/workflows/unit-tests.yml"
- "Makefile"
- "**/*.go"
- "**/*.c"
- "**/*.h"
- "**/*.sh"
- "**/*.ps1"
types:
- opened
- reopened
@@ -18,15 +20,15 @@ on:
- ready_for_review
jobs:
linux-windows-build-test:
name: Linux & Windows Test
linux-build-test:
name: Linux Build & Test
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Go 1.25
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.6"
go-version: "1.25.8"
- name: OS Packages
run: |
@@ -64,16 +66,59 @@ jobs:
- name: Test Linux
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
windows-build-test:
name: Windows Build & Test
runs-on: windows-latest
timeout-minutes: 180
steps:
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
- name: Install make
run: choco install make -y --no-progress
- name: Check Out Code
uses: actions/checkout@v6
- name: Git Fetch Tags
run: git fetch --prune --unshallow --tags -f
- name: Make Windows (amd64)
run: make windows-amd64
- name: Determine go-tests flags
id: go-tests-flags
shell: pwsh
run: |
$baseSha = "${{ github.event.pull_request.base.sha }}"
$headSha = "${{ github.sha }}"
$changedFiles = git diff --name-only $baseSha $headSha
if ($changedFiles | Select-String -Pattern '^(implant/|server/gogo/|server/generate/)') {
"args=" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
} else {
"args=--skip-generate" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
}
- name: Test Windows
shell: pwsh
run: |
make clean
make
./go-tests.ps1 ${{ steps.go-tests-flags.outputs.args }}
macos-build-test:
name: MacOS Test
runs-on: macos-latest
timeout-minutes: 180
steps:
- name: Go 1.25
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.6"
go-version: "1.25.8"
- name: Check Out Code
uses: actions/checkout@v6
@@ -108,10 +153,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Go 1.25
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.25.6"
go-version: "1.25.8"
- name: Check Out Code
uses: actions/checkout@v6
+3
View File
@@ -1,3 +1,6 @@
/crush
/.vscode
/.vscode/*
/.devcontainer
/.idea
+1 -24
View File
@@ -1,41 +1,18 @@
# Repository Guidelines
## Project Structure & Module Organization
- `client/` holds the CLI client and interactive commands.
- `server/` contains the server runtime, C2 logic, configs, and web assets.
- `implant/` is the implant runtime and transport implementations.
- `protobuf/` stores protobuf definitions and generated Go types.
- `util/` is shared helpers (crypto, encoders, minisign, etc.).
- `docs/sliver-docs/` is the Next.js documentation site and prebuild scripts.
- `vendor/` is the vendored Go dependency tree used by builds.
- Build outputs land in the repo root as `sliver-client*` and `sliver-server*`.
## Build, Test, and Development Commands
- `make` builds server + client using vendored modules and runs `go-assets.sh`.
- `make client` builds a CGO-free client; `make debug` keeps symbols.
- `make pb` regenerates protobuf stubs from `protobuf/`.
- `./go-tests.sh` runs the curated Go test suite with build tags.
- `./go-tests.sh` runs the Go test suite, this is the only way you should run unit tests.
- Docs site: `cd docs/sliver-docs && npm install && npm run dev` for local dev; `npm run build` for a static build.
- Go toolchain: the Makefile enforces Go >= 1.24.
## Coding Style & Naming Conventions
- Run `gofmt` on all Go code; keep package names lowercase and idiomatic.
- Avoid `CGO` and empty interfaces; prefer explicit types and interfaces.
- Do not import `server` packages from `client`.
- If `math/rand` is required, import it as `insecureRand` and avoid security use.
## Testing Guidelines
- Go tests follow standard `_test.go` naming within each package.
- Prefer `docker build --target test --build-arg GO_TESTS_FLAGS=--skip-generate .`
- Add or update tests when behavior changes; some server packages run longer tests.
## Commit & Pull Request Guidelines
- Commits must be signed for PRs to `master`; work in a feature branch.
- Commit subjects are short and imperative (e.g., "Fix ...", "Add ...", "Update ...", "Bump ...").
- Changes to `vendor/` should be in a distinct commit.
- PRs should link the tracking issue, note tests run, and confirm `gofmt` plus unit tests as applicable.
## Security Notes
- Default to secure configuration; fail closed rather than open.
- Avoid insecure algorithms (MD5/SHA1/AES-ECB); prefer AES-GCM and modern curves.
- Treat user input as untrusted: canonicalize paths and use restrictive file permissions.
+274 -144
View File
@@ -3,7 +3,7 @@
#
GO ?= go
ARTIFACT_SUFFIX ?=
ARTIFACT_SUFFIX ?=
ENV =
TAGS ?= -tags go_sqlite
CGO_ENABLED = 0
@@ -12,21 +12,8 @@ ifneq (,$(findstring cgo_sqlite,$(TAGS)))
CGO_ENABLED = 1
endif
#
# Prerequisites
#
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
EXECUTABLES = uname sed git date cut $(GO)
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
#
# Build Information
#
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 24
MIN_SUPPORTED_GO_MINOR_VERSION = 25
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
@@ -34,151 +21,294 @@ ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
ifneq ($(OS),Windows_NT)
#
# Prerequisites
#
# https://stackoverflow.com/questions/5618615/check-if-a-program-exists-from-a-makefile
EXECUTABLES = uname sed git date cut $(GO)
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
#
# Build Information
#
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
LDFLAGS = -ldflags "-s -w \
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
# Debug builds shouldn't be stripped (-s -w flags)
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
SED_INPLACE := sed -i
STATIC_TARGET := linux
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
# Debug builds shouldn't be stripped (-s -w flags)
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
SED_INPLACE := sed -i
STATIC_TARGET := linux
UNAME_S := $(shell uname -s)
UNAME_P := $(shell uname -p)
# Programs required for generating protobuf/grpc files
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
ifeq ($(MAKECMDGOALS), pb)
K := $(foreach exec,$(PB_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing protobuf util $(exec) in PATH")))
endif
# *** Darwin ***
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
STATIC_TARGET := macos
endif
# If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm)
ifeq ($(MAKECMDGOALS), )
ifeq ($(origin GOARCH), undefined)
ENV += GOARCH=arm64
endif
endif
endif
ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif
# *** Darwin ***
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
STATIC_TARGET := macos
endif
# If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm)
ifeq ($(MAKECMDGOALS), )
ifeq ($(origin GOARCH), undefined)
ENV += GOARCH=arm64
endif
endif
endif
ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif
#
# Targets
#
.PHONY: default
default: clean validate-go-version
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
.PHONY: client
client: clean .downloaded_assets validate-go-version
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
.PHONY: macos-amd64
macos-amd64: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: macos-arm64
macos-arm64: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: linux-amd64
linux-amd64: clean .downloaded_assets validate-go-version
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: linux-arm64
linux-arm64: clean .downloaded_assets validate-go-version
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: windows-amd64
windows-amd64: clean .downloaded_assets validate-go-version
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
.PHONY: clients
clients: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-arm64$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-386$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-arm64$(ARTIFACT_SUFFIX) ./client
GOOS=windows GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-386$(ARTIFACT_SUFFIX).exe ./client
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-amd64$(ARTIFACT_SUFFIX).exe ./client
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-arm64$(ARTIFACT_SUFFIX).exe ./client
GOOS=freebsd GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
.PHONY: servers
servers: clean .downloaded_assets validate-go-version
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-amd64$(ARTIFACT_SUFFIX) ./server
.PHONY: pb
pb:
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
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) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) CGO_ENABLED=0 $(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 ;\
elif [ $(GO_MAJOR_VERSION) -lt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
exit 1; \
elif [ $(GO_MINOR_VERSION) -lt $(MIN_SUPPORTED_GO_MINOR_VERSION) ] ; then \
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
exit 1; \
fi
.PHONY: clean-all
clean-all: clean
rm -rf ./server/assets/fs/darwin/amd64
rm -rf ./server/assets/fs/darwin/arm64
rm -rf ./server/assets/fs/windows/amd64
rm -rf ./server/assets/fs/linux/amd64
rm -f ./server/assets/fs/*.zip
rm -f ./.downloaded_assets
.PHONY: clean
clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets:
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
#
# Targets
# >>> WINDOWS <<<
#
.PHONY: default
default: clean validate-go-version
env -u GOOS -u GOARCH $(MAKE) GOOS= GOARCH= .downloaded_assets
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
else
# Allows you to build a CGO-free client for any target e.g. `GOOS=windows GOARCH=arm64 make client`
# NOTE: WireGuard is not supported on all platforms, but most 64-bit GOOS/GOARCH combinations should work.
.PHONY: client
client: clean .downloaded_assets validate-go-version
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client ./client
SHELL := cmd.exe
.SHELLFLAGS := /C
.PHONY: macos-amd64
macos-amd64: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
GO_VERSION := $(patsubst go%,%,$(strip $(shell $(GO) env GOVERSION)))
GO_MAJOR_VERSION := $(word 1,$(subst ., ,$(GO_VERSION)))
GO_MINOR_VERSION := $(word 2,$(subst ., ,$(GO_VERSION)))
.PHONY: macos-arm64
macos-arm64: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
LDFLAGS = -ldflags "-s -w -X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
.PHONY: linux-amd64
linux-amd64: clean .downloaded_assets validate-go-version
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
.PHONY: linux-arm64
linux-arm64: clean .downloaded_assets validate-go-version
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: windows-amd64
windows-amd64: clean .downloaded_assets validate-go-version
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX).exe ./client
.PHONY: clients
clients: clean .downloaded_assets validate-go-version
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_macos-arm64$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-386$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_linux-arm64$(ARTIFACT_SUFFIX) ./client
GOOS=windows GOARCH=386 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-386$(ARTIFACT_SUFFIX).exe ./client
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-amd64$(ARTIFACT_SUFFIX).exe ./client
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_windows-arm64$(ARTIFACT_SUFFIX).exe ./client
GOOS=freebsd GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-amd64$(ARTIFACT_SUFFIX) ./client
GOOS=freebsd GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX) ./client
.PHONY: servers
servers: clean .downloaded_assets validate-go-version
GOOS=windows GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe ./server
GOOS=windows GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe ./server
GOOS=linux GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-amd64$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_linux-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=arm64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-arm64$(ARTIFACT_SUFFIX) ./server
GOOS=darwin GOARCH=amd64 $(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server_darwin-amd64$(ARTIFACT_SUFFIX) ./server
.PHONY: pb
pb:
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
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) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) CGO_ENABLED=0 $(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 ;\
elif [ $(GO_MAJOR_VERSION) -lt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
exit 1; \
elif [ $(GO_MINOR_VERSION) -lt $(MIN_SUPPORTED_GO_MINOR_VERSION) ] ; then \
echo '$(GO_VERSION_VALIDATION_ERR_MSG)';\
exit 1; \
fi
.PHONY: clean-all
clean-all: clean
rm -rf ./server/assets/fs/darwin/amd64
rm -rf ./server/assets/fs/darwin/arm64
rm -rf ./server/assets/fs/windows/amd64
rm -rf ./server/assets/fs/linux/amd64
rm -f ./server/assets/fs/*.zip
rm -f ./.downloaded_assets
.PHONY: clean
clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets:
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
COMMA := ,
ifeq ($(MAKECMDGOALS), linux)
LDFLAGS = -ldflags "-s -w -extldflags '-static' -X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
endif
define windows_exec
$(strip $(foreach envvar,$(1),set "$(envvar)" && ))$(2)
endef
define windows_go_build
$(call windows_exec,$(ENV) GOOS=$(1) GOARCH=$(2) CGO_ENABLED=$(3),"$(GO)" build $(4) $(TAGS)$(COMMA)$(5) $(6) -o $(7) ./$(5))
endef
.PHONY: default
default: clean validate-go-version
$(call windows_exec,$(ENV) GOOS= GOARCH=,"$(MAKE)" GOOS= GOARCH= .downloaded_assets)
$(call windows_go_build,$(GOOS),$(GOARCH),$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: client
client: clean .downloaded_assets validate-go-version
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client)
.PHONY: macos-amd64
macos-amd64: clean .downloaded_assets validate-go-version
$(call windows_go_build,darwin,amd64,$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,darwin,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: macos-arm64
macos-arm64: clean .downloaded_assets validate-go-version
$(call windows_go_build,darwin,arm64,$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,darwin,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: linux-amd64
linux-amd64: clean .downloaded_assets validate-go-version
$(call windows_go_build,linux,amd64,$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: linux-arm64
linux-arm64: clean .downloaded_assets validate-go-version
$(call windows_go_build,linux,arm64,$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: windows-amd64
windows-amd64: clean .downloaded_assets validate-go-version
$(call windows_go_build,windows,amd64,$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,windows,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX).exe)
.PHONY: clients
clients: clean .downloaded_assets validate-go-version
$(call windows_go_build,darwin,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_macos-amd64$(ARTIFACT_SUFFIX))
$(call windows_go_build,darwin,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_macos-arm64$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,386,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_linux-386$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_linux-amd64$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_linux-arm64$(ARTIFACT_SUFFIX))
$(call windows_go_build,windows,386,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_windows-386$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,windows,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_windows-amd64$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,windows,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_windows-arm64$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,freebsd,amd64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_freebsd-amd64$(ARTIFACT_SUFFIX))
$(call windows_go_build,freebsd,arm64,0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client_freebsd-arm64$(ARTIFACT_SUFFIX))
.PHONY: servers
servers: clean .downloaded_assets validate-go-version
$(call windows_go_build,windows,amd64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_windows-amd64$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,windows,arm64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_windows-arm64$(ARTIFACT_SUFFIX).exe)
$(call windows_go_build,linux,amd64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_linux-amd64$(ARTIFACT_SUFFIX))
$(call windows_go_build,linux,arm64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_linux-arm64$(ARTIFACT_SUFFIX))
$(call windows_go_build,darwin,arm64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_darwin-arm64$(ARTIFACT_SUFFIX))
$(call windows_go_build,darwin,amd64,0,-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server_darwin-amd64$(ARTIFACT_SUFFIX))
.PHONY: pb
pb: validate-pb-compilers
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
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
$(call windows_go_build,$(GOOS),$(GOARCH),$(CGO_ENABLED),-mod=vendor,server,$(LDFLAGS_DEBUG),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor,client,$(LDFLAGS_DEBUG),sliver-client$(ARTIFACT_SUFFIX))
.PHONY: validate-pb-compilers
validate-pb-compilers:
@for %%P in ($(PB_COMPILERS)) do @where.exe %%P >NUL || (echo Missing protobuf util %%P in PATH & exit /b 1)
validate-go-version:
@if $(GO_MAJOR_VERSION) GTR $(MIN_SUPPORTED_GO_MAJOR_VERSION) (exit /b 0) else if $(GO_MAJOR_VERSION) LSS $(MIN_SUPPORTED_GO_MAJOR_VERSION) (echo $(GO_VERSION_VALIDATION_ERR_MSG) & exit /b 1) else if $(GO_MINOR_VERSION) LSS $(MIN_SUPPORTED_GO_MINOR_VERSION) (echo $(GO_VERSION_VALIDATION_ERR_MSG) & exit /b 1)
.PHONY: clean-all
clean-all: clean
-rmdir /S /Q server\assets\fs\darwin\amd64
-rmdir /S /Q server\assets\fs\darwin\arm64
-rmdir /S /Q server\assets\fs\windows\amd64
-rmdir /S /Q server\assets\fs\linux\amd64
-del /Q /F server\assets\fs\*.zip 2>NUL
-del /Q /F .downloaded_assets 2>NUL
.PHONY: clean
clean:
-del /Q /F sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe 2>NUL
.downloaded_assets:
$(call windows_exec,$(ENV),"$(GO)" run -mod=vendor ./util/cmd/assets)
@type NUL > .downloaded_assets
endif
+2
View File
@@ -6,6 +6,8 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
### Features
- Dynamic code generation
+1
View File
@@ -124,4 +124,5 @@ under certain conditions; type 'licenses' for details.`)
saveAssetVersion(appDir)
}
_, _ = LoadSettings()
_, _ = LoadSpoofMetadataConfig()
}
+17 -7
View File
@@ -34,15 +34,25 @@ const (
ConfigDirName = "configs"
)
// ClientWGConfig - Optional WireGuard wrapper configuration for multiplayer.
type ClientWGConfig struct {
ServerPubKey string `json:"server_pub_key"`
ClientPrivateKey string `json:"client_private_key"`
ClientPubKey string `json:"client_pub_key"`
ClientIP string `json:"client_ip"`
ServerIP string `json:"server_ip"`
}
// ClientConfig - Client JSON config
type ClientConfig struct {
Operator string `json:"operator"` // This value is actually ignored for the most part (cert CN is used instead)
LHost string `json:"lhost"`
LPort int `json:"lport"`
Token string `json:"token"`
CACertificate string `json:"ca_certificate"`
PrivateKey string `json:"private_key"`
Certificate string `json:"certificate"`
Operator string `json:"operator"` // This value is actually ignored for the most part (cert CN is used instead)
LHost string `json:"lhost"`
LPort int `json:"lport"`
Token string `json:"token"`
CACertificate string `json:"ca_certificate"`
PrivateKey string `json:"private_key"`
Certificate string `json:"certificate"`
WG *ClientWGConfig `json:"wg,omitempty"`
}
// GetConfigDir - Returns the path to the config dir
+53
View File
@@ -22,6 +22,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
@@ -31,6 +32,18 @@ const (
settingsLegacyFileName = "tui-settings.json"
)
const (
// Accepted values for tui-settings.yaml "prompt:":
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
// - basic: show "sliver >" (no prefix)
// - custom: render full prompt from prompt_template
PromptStyleHost = "host"
PromptStyleOperatorHost = "operator-host"
PromptStyleBasic = "basic"
PromptStyleCustom = "custom"
)
// ClientSettings - Client JSON config
type ClientSettings struct {
TableStyle string `json:"tables" yaml:"tables"`
@@ -41,6 +54,8 @@ type ClientSettings struct {
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
UserConnect bool `json:"user_connect" yaml:"user_connect"`
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
PromptStyle string `json:"prompt" yaml:"prompt"`
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
}
// LoadSettings - Load the client settings from disk
@@ -67,6 +82,8 @@ func LoadSettings() (*ClientSettings, error) {
return defaultSettings(), err
}
// Ensure any missing/unknown values are coerced to a supported prompt style.
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
if err := SaveSettings(settings); err != nil {
return settings, err
}
@@ -78,6 +95,38 @@ func LoadSettings() (*ClientSettings, error) {
return settings, nil
}
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
// default if the value is empty/unknown.
func NormalizePromptStyle(v string) string {
switch strings.ToLower(strings.TrimSpace(v)) {
case PromptStyleOperatorHost:
return PromptStyleOperatorHost
case PromptStyleHost:
return PromptStyleHost
case PromptStyleBasic:
return PromptStyleBasic
case PromptStyleCustom:
return PromptStyleCustom
// Backward compatible aliases.
case "show host":
return PromptStyleHost
case "show user and host":
return PromptStyleOperatorHost
case "show operator and host":
return PromptStyleOperatorHost
case "operator@host":
return PromptStyleOperatorHost
case "user@host":
return PromptStyleOperatorHost
case "":
return PromptStyleHost
default:
return PromptStyleHost
}
}
func defaultSettings() *ClientSettings {
return &ClientSettings{
TableStyle: "SliverDefault",
@@ -87,9 +136,13 @@ func defaultSettings() *ClientSettings {
AlwaysOverflow: false,
VimMode: false,
ConsoleLogs: true,
PromptStyle: PromptStyleHost,
PromptTemplate: DefaultPromptTemplate,
}
}
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
// SaveSettings - Save the current settings to disk
func SaveSettings(settings *ClientSettings) error {
rootDir, _ := filepath.Abs(GetRootAppDir())
+22
View File
@@ -21,6 +21,8 @@ always_overflow: true
vim_mode: true
user_connect: true
console_logs: false
prompt: "basic"
prompt_template: "{{.Host}}"
`)
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
t.Fatalf("failed to write yaml settings: %v", err)
@@ -54,6 +56,12 @@ console_logs: false
if settings.ConsoleLogs {
t.Fatalf("expected ConsoleLogs false")
}
if settings.PromptStyle != PromptStyleBasic {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Host}}" {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
}
}
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
@@ -71,6 +79,8 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
VimMode: true,
UserConnect: true,
ConsoleLogs: false,
PromptStyle: PromptStyleOperatorHost,
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
@@ -87,6 +97,12 @@ func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
if settings.TableStyle != "Legacy" {
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleOperatorHost {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
}
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected yaml settings to exist: %v", err)
}
@@ -108,6 +124,12 @@ func TestLoadSettingsWritesDefault(t *testing.T) {
if settings.TableStyle != "SliverDefault" {
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleHost {
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
}
if settings.PromptTemplate != DefaultPromptTemplate {
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
}
rootDir, _ := filepath.Abs(GetRootAppDir())
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected default settings to be written: %v", err)
+216
View File
@@ -0,0 +1,216 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2026 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
const spoofMetadataFileName = "spoof-metadata.yaml"
const defaultSpoofMetadataConfigTemplate = `# Sliver executable metadata spoofing configuration.
# Used by generate commands when --spoof-metadata is set.
#
# Fields that reference file content support either:
# - path: read bytes from a local file path
# - base64: inline base64 file content
# Do not set both path and base64 for the same object.
#
# Example 1: Minimal path-based metadata cloning (most common)
pe:
source:
name: metadata-source.exe
path: ""
# Optional icon donor. Reserved for future standalone icon mutation support.
icon:
name: icon.ico
path: ""
# Example 2: Inline/base64 source (uncomment and remove "path" above)
# source:
# name: metadata-source.exe
# base64: "TVqQAAMAAAAEAAAA..."
# Example 3: Optional PE structure overrides (advanced)
# Applied after metadata/resources are cloned from source.
# Omitted fields keep cloned values.
# resource_directory:
# characteristics: 0
# time_date_stamp: 0
# major_version: 1
# minor_version: 0
# number_of_named_entries: 0
# number_of_id_entries: 0
#
# resource_directory_entries:
# - name: 1
# offset_to_data: 0
#
# resource_data_entries:
# - offset_to_data: 0
# size: 0
# code_page: 0
# reserved: 0
#
# export_directory:
# characteristics: 0
# time_date_stamp: 0
# major_version: 1
# minor_version: 0
# name: 0
# base: 0
# number_of_functions: 0
# number_of_names: 0
# address_of_functions: 0
# address_of_names: 0
# address_of_name_ordinals: 0
`
// SpoofMetadataConfig stores default metadata spoofing inputs for generate commands.
type SpoofMetadataConfig struct {
PE *PESpoofMetadataConfig `json:"pe,omitempty" yaml:"pe,omitempty"`
}
// PESpoofMetadataConfig stores PE-specific spoofing inputs.
type PESpoofMetadataConfig struct {
Source *SpoofMetadataDataSource `json:"source,omitempty" yaml:"source,omitempty"`
Icon *SpoofMetadataDataSource `json:"icon,omitempty" yaml:"icon,omitempty"`
ResourceDirectory *ImageResourceDirectory `json:"resource_directory,omitempty" yaml:"resource_directory,omitempty"`
ResourceDirectoryEntries []*ImageResourceDirectoryEntry `json:"resource_directory_entries,omitempty" yaml:"resource_directory_entries,omitempty"`
ResourceDataEntries []*ImageResourceDataEntry `json:"resource_data_entries,omitempty" yaml:"resource_data_entries,omitempty"`
ExportDirectory *ImageExportDirectory `json:"export_directory,omitempty" yaml:"export_directory,omitempty"`
}
// SpoofMetadataDataSource points to bytes that should be sent to the server.
// Either Path or Base64 may be set.
type SpoofMetadataDataSource struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Base64 string `json:"base64,omitempty" yaml:"base64,omitempty"`
}
type ImageResourceDirectory struct {
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
NumberOfNamedEntries uint32 `json:"number_of_named_entries,omitempty" yaml:"number_of_named_entries,omitempty"`
NumberOfIDEntries uint32 `json:"number_of_id_entries,omitempty" yaml:"number_of_id_entries,omitempty"`
}
type ImageResourceDirectoryEntry struct {
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
}
type ImageResourceDataEntry struct {
OffsetToData uint32 `json:"offset_to_data,omitempty" yaml:"offset_to_data,omitempty"`
Size uint32 `json:"size,omitempty" yaml:"size,omitempty"`
CodePage uint32 `json:"code_page,omitempty" yaml:"code_page,omitempty"`
Reserved uint32 `json:"reserved,omitempty" yaml:"reserved,omitempty"`
}
type ImageExportDirectory struct {
Characteristics uint32 `json:"characteristics,omitempty" yaml:"characteristics,omitempty"`
TimeDateStamp uint32 `json:"time_date_stamp,omitempty" yaml:"time_date_stamp,omitempty"`
MajorVersion uint32 `json:"major_version,omitempty" yaml:"major_version,omitempty"`
MinorVersion uint32 `json:"minor_version,omitempty" yaml:"minor_version,omitempty"`
Name uint32 `json:"name,omitempty" yaml:"name,omitempty"`
Base uint32 `json:"base,omitempty" yaml:"base,omitempty"`
NumberOfFunctions uint32 `json:"number_of_functions,omitempty" yaml:"number_of_functions,omitempty"`
NumberOfNames uint32 `json:"number_of_names,omitempty" yaml:"number_of_names,omitempty"`
AddressOfFunctions uint32 `json:"address_of_functions,omitempty" yaml:"address_of_functions,omitempty"`
AddressOfNames uint32 `json:"address_of_names,omitempty" yaml:"address_of_names,omitempty"`
AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals,omitempty" yaml:"address_of_name_ordinals,omitempty"`
}
// SpoofMetadataConfigPath returns the spoof metadata config file path.
func SpoofMetadataConfigPath() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
return filepath.Join(rootDir, spoofMetadataFileName)
}
// LoadSpoofMetadataConfig loads spoof metadata config from disk, writing defaults
// if the file is missing.
func LoadSpoofMetadataConfig() (*SpoofMetadataConfig, error) {
config := defaultSpoofMetadataConfig()
configPath := SpoofMetadataConfigPath()
data, err := os.ReadFile(configPath)
if err != nil {
if !os.IsNotExist(err) {
return defaultSpoofMetadataConfig(), err
}
if err := os.WriteFile(configPath, []byte(defaultSpoofMetadataConfigTemplate), 0o600); err != nil {
return defaultSpoofMetadataConfig(), err
}
data = []byte(defaultSpoofMetadataConfigTemplate)
}
if err := yaml.Unmarshal(data, config); err != nil {
return defaultSpoofMetadataConfig(), err
}
normalizeSpoofMetadataConfig(config)
return config, nil
}
// SaveSpoofMetadataConfig writes spoof metadata config to disk.
func SaveSpoofMetadataConfig(config *SpoofMetadataConfig) error {
if config == nil {
config = defaultSpoofMetadataConfig()
}
normalizeSpoofMetadataConfig(config)
data, err := yaml.Marshal(config)
if err != nil {
return err
}
return os.WriteFile(SpoofMetadataConfigPath(), data, 0o600)
}
func normalizeSpoofMetadataConfig(config *SpoofMetadataConfig) {
if config.PE == nil {
config.PE = &PESpoofMetadataConfig{}
}
if config.PE.Source == nil {
config.PE.Source = &SpoofMetadataDataSource{Name: "metadata-source.exe"}
}
if config.PE.Icon == nil {
config.PE.Icon = &SpoofMetadataDataSource{Name: "icon.ico"}
}
}
func defaultSpoofMetadataConfig() *SpoofMetadataConfig {
return &SpoofMetadataConfig{
PE: &PESpoofMetadataConfig{
Source: &SpoofMetadataDataSource{
Name: "metadata-source.exe",
Path: "",
},
Icon: &SpoofMetadataDataSource{
Name: "icon.ico",
Path: "",
},
},
}
}
+2
View File
@@ -52,6 +52,8 @@ func init() {
rootCmd.TraverseChildren = true
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
rootCmd.PersistentFlags().Bool(disableWGFlag, false, "connect to multiplayer directly even if the operator config includes a WireGuard wrapper")
rootCmd.PersistentFlags().Bool(requireWGFlag, false, "require the operator config's WireGuard wrapper for multiplayer connections")
// Create the console client, without any RPC or commands bound to it yet.
// This created before anything so that multiple commands can make use of
+6 -6
View File
@@ -26,16 +26,16 @@ import (
"github.com/bishopfox/sliver/client/forms"
)
func selectConfig() *assets.ClientConfig {
func selectConfig() (string, *assets.ClientConfig) {
configs := assets.GetConfigs()
if len(configs) == 0 {
return nil
return "", nil
}
if len(configs) == 1 {
for _, config := range configs {
return config
for key, config := range configs {
return key, config
}
}
@@ -49,8 +49,8 @@ func selectConfig() *assets.ClientConfig {
err := forms.Select("Select a server:", keys, &selection)
if err != nil {
fmt.Println(err.Error())
return nil
return "", nil
}
return configs[selection]
return selection, configs[selection]
}
+26
View File
@@ -0,0 +1,26 @@
package cli
import (
"context"
"fmt"
"os"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
func handleConnectionLost(ln *grpc.ClientConn) {
if ln == nil {
return
}
currentState := ln.GetState()
// currentState should be "Ready" when the connection is established.
if ln.WaitForStateChange(context.Background(), currentState) {
newState := ln.GetState()
// newState will be "Idle" if the connection is lost.
if newState == connectivity.Idle {
fmt.Println("\nLost connection to server. Exiting now.")
os.Exit(1)
}
}
}
+7 -29
View File
@@ -19,9 +19,7 @@ package cli
*/
import (
"context"
"fmt"
"os"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command"
@@ -30,7 +28,6 @@ import (
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
// consoleCmd generates the console with required pre/post runners.
@@ -46,16 +43,17 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
}
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
var ln *grpc.ClientConn
pre = func(cmd *cobra.Command, _ []string) error {
if err := applyMultiplayerConnectMode(cmd); err != nil {
return err
}
configs := assets.GetConfigs()
if len(configs) == 0 {
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
return nil
}
config := selectConfig()
configKey, config := selectConfig()
if config == nil {
return nil
}
@@ -72,40 +70,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
}
var rpc rpcpb.SliverRPCClient
var ln *grpc.ClientConn
rpc, ln, err = transport.MTLSConnect(config)
if err != nil {
fmt.Printf("Connection to server failed %s", err)
return nil
}
// Wait for any connection state changes and exit if the connection is lost.
go handleConnectionLost(ln)
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
}
// Close the RPC connection once exiting
post = func(_ *cobra.Command, _ []string) error {
if ln != nil {
return ln.Close()
}
return nil
return con.CloseConnection()
}
return pre, post
}
func handleConnectionLost(ln *grpc.ClientConn) {
currentState := ln.GetState()
// currentState should be "Ready" when the connection is established.
if ln.WaitForStateChange(context.Background(), currentState) {
newState := ln.GetState()
// newState will be "Idle" if the connection is lost.
if newState == connectivity.Idle {
fmt.Println("\nLost connection to server. Exiting now.")
os.Exit(1)
}
}
}
+5 -1
View File
@@ -43,6 +43,10 @@ func mcpCmd(con *console.SliverClient) *cobra.Command {
}
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
if err := applyMultiplayerConnectMode(cmd); err != nil {
return err
}
configPath, _ := cmd.Flags().GetString("config")
config, err := loadMCPClientConfig(configPath)
if err != nil {
@@ -55,7 +59,7 @@ func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
fmt.Printf("Connection to server failed %s\n", err)
return nil
}
defer ln.Close()
defer transport.CloseGRPCConnection(ln)
con.Rpc = rpc
go handleConnectionLost(ln)
+42
View File
@@ -0,0 +1,42 @@
package cli
import (
"fmt"
"github.com/bishopfox/sliver/client/transport"
"github.com/spf13/cobra"
)
const (
requireWGFlag = "require-wg"
disableWGFlag = "disable-wg"
)
func applyMultiplayerConnectMode(cmd *cobra.Command) error {
if cmd == nil {
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectAuto)
return nil
}
requireWG, err := cmd.Flags().GetBool(requireWGFlag)
if err != nil {
return err
}
disableWG, err := cmd.Flags().GetBool(disableWGFlag)
if err != nil {
return err
}
if requireWG && disableWG {
return fmt.Errorf("--%s and --%s cannot be used together", requireWGFlag, disableWGFlag)
}
mode := transport.MultiplayerConnectAuto
switch {
case requireWG:
mode = transport.MultiplayerConnectRequireWG
case disableWG:
mode = transport.MultiplayerConnectDisableWG
}
transport.SetMultiplayerConnectMode(mode)
return nil
}
+1
View File
@@ -12,6 +12,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
## Sub-packages
- `ai/` – Implements the 'ai' Bubble Tea layout preview command for the Sliver client console.
- `alias/` – Implements the 'alias' command group for the Sliver client console.
- `armory/` – Implements the 'armory' command group for the Sliver client console.
- `backdoor/` – Implements the 'backdoor' command group for the Sliver client console.
+5
View File
@@ -0,0 +1,5 @@
# client/command/ai
Implements the `ai` command for the Sliver client console.
The TUI loads server-backed AI conversation threads over gRPC, submits prompts to the server, and refreshes when `AIConversationEvent` updates arrive with assistant replies or failure messages.
+145
View File
@@ -0,0 +1,145 @@
package ai
import (
"fmt"
"strings"
"time"
tea "charm.land/bubbletea/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/charmbracelet/colorprofile"
"github.com/spf13/cobra"
"golang.org/x/term"
)
// AICmd launches the AI conversation TUI.
func AICmd(_ *cobra.Command, con *console.SliverClient, _ []string) {
if con == nil || con.Rpc == nil {
if con != nil {
con.PrintErrorf("Connect to a server before using `ai`.\n")
}
return
}
listenerID, listener := con.CreateEventListener()
defer con.RemoveEventListener(listenerID)
model := newAIModel(con, buildAIContext(con), listener)
width, height := 100, 30
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
width, height = w, h
}
program := tea.NewProgram(
model,
tea.WithWindowSize(width, height),
tea.WithColorProfile(colorprofile.TrueColor),
)
if _, err := program.Run(); err != nil {
con.PrintErrorf("AI TUI error: %s\n", err)
}
}
type aiContext struct {
target aiTargetSummary
connection aiConnectionSummary
status string
}
type aiTargetSummary struct {
Label string
Host string
OS string
Arch string
C2 string
Mode string
Details []string
}
type aiConnectionSummary struct {
Profile string
Server string
Operator string
State string
}
func buildAIContext(con *console.SliverClient) aiContext {
ctx := aiContext{
target: aiTargetSummary{
Label: "No active target",
Host: "Select a session or beacon with `use`",
Mode: "offline preview",
C2: "n/a",
OS: "unknown",
Arch: "unknown",
},
connection: aiConnectionSummary{
Profile: "<disconnected>",
Server: "<unknown>",
Operator: "<unknown>",
State: "idle",
},
status: "Loading AI conversations from the server...",
}
if con != nil {
if details, state, ok := con.CurrentConnection(); ok {
ctx.connection.State = strings.ToLower(state.String())
if details != nil && details.Config != nil {
ctx.connection.Profile = fallback(details.ConfigKey, "<profile unavailable>")
ctx.connection.Server = fmt.Sprintf("%s:%d", details.Config.LHost, details.Config.LPort)
ctx.connection.Operator = fallback(details.Config.Operator, "<unknown>")
}
}
session, beacon := con.ActiveTarget.Get()
switch {
case session != nil:
ctx.target = aiTargetSummary{
Label: fmt.Sprintf("Session %s", fallback(session.Name, session.ID)),
Host: fallback(session.Hostname, "<unknown host>"),
OS: fallback(session.OS, "unknown"),
Arch: fallback(session.Arch, "unknown"),
C2: fallback(session.ActiveC2, "unknown"),
Mode: "interactive session",
Details: []string{
fmt.Sprintf("User: %s", fallback(session.Username, "<unknown>")),
fmt.Sprintf("PID: %d", session.PID),
fmt.Sprintf("Remote: %s", fallback(session.RemoteAddress, "<unknown>")),
},
}
case beacon != nil:
ctx.target = aiTargetSummary{
Label: fmt.Sprintf("Beacon %s", fallback(beacon.Name, beacon.ID)),
Host: fallback(beacon.Hostname, "<unknown host>"),
OS: fallback(beacon.OS, "unknown"),
Arch: fallback(beacon.Arch, "unknown"),
C2: fallback(beacon.ActiveC2, "unknown"),
Mode: "asynchronous beacon",
Details: []string{
fmt.Sprintf("User: %s", fallback(beacon.Username, "<unknown>")),
fmt.Sprintf("Interval: %s", time.Duration(beacon.Interval).String()),
fmt.Sprintf("Next checkin: %s", formatUnix(beacon.NextCheckin)),
},
}
}
}
return ctx
}
func fallback(value, def string) string {
value = strings.TrimSpace(value)
if value == "" {
return def
}
return value
}
func formatUnix(ts int64) string {
if ts <= 0 {
return "<unknown>"
}
return time.Unix(ts, 0).Local().Format("2006-01-02 15:04:05")
}
+31
View File
@@ -0,0 +1,31 @@
package ai
import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
// Commands returns the ai command.
func Commands(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{newAICommand(consts.SliverCoreHelpGroup, con)}
}
// ServerCommands returns the ai command for the top-level client REPL.
func ServerCommands(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{newAICommand(consts.GenericHelpGroup, con)}
}
func newAICommand(groupID string, con *console.SliverClient) *cobra.Command {
return &cobra.Command{
Use: consts.AIStr,
Short: "Open the Sliver AI conversation TUI",
Long: help.GetHelpFor([]string{consts.AIStr}),
Args: cobra.NoArgs,
GroupID: groupID,
Run: func(cmd *cobra.Command, args []string) {
AICmd(cmd, con, args)
},
}
}
+489
View File
@@ -0,0 +1,489 @@
package ai
import (
"context"
"net"
"sync"
"testing"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/proto"
)
func TestLoadAIStateCmdLoadsConversationOverRPC(t *testing.T) {
server := &aiRPCServer{
providersResp: &clientpb.AIProviderConfigs{
Providers: []*clientpb.AIProviderConfig{{Name: "openai", Configured: true}},
Config: &clientpb.AIConfigSummary{
Provider: "openai",
Model: "gpt-test",
Valid: true,
},
},
conversationsResp: &clientpb.AIConversations{
Conversations: []*clientpb.AIConversation{
{ID: "conv-1", Provider: "openai", Model: "gpt-test", Title: "First"},
{ID: "conv-2", Provider: "openai", Model: "gpt-test", Title: "Second"},
},
},
conversationByID: map[string]*clientpb.AIConversation{
"conv-1": {
ID: "conv-1",
Provider: "openai",
Model: "gpt-test",
Title: "First",
Messages: []*clientpb.AIConversationMessage{
{Role: "user", Content: "Hello"},
},
},
},
}
rpcClient, cleanup := newAITestRPCClient(t, server)
defer cleanup()
cmd := loadAIStateCmd(&console.SliverClient{Rpc: rpcClient}, "")
msg := cmd()
loaded, ok := msg.(aiLoadedMsg)
if !ok {
t.Fatalf("expected aiLoadedMsg, got %T", msg)
}
if loaded.selectedID != "conv-1" {
t.Fatalf("unexpected selected conversation id: got=%q want=%q", loaded.selectedID, "conv-1")
}
if loaded.conversation == nil || loaded.conversation.GetID() != "conv-1" {
t.Fatalf("unexpected loaded conversation: %+v", loaded.conversation)
}
if len(loaded.conversations) != 2 {
t.Fatalf("unexpected conversation count: got=%d want=%d", len(loaded.conversations), 2)
}
server.mu.Lock()
defer server.mu.Unlock()
if server.getProvidersCalls != 1 {
t.Fatalf("expected GetAIProviders to be called once, got %d", server.getProvidersCalls)
}
if server.getConversationsCalls != 1 {
t.Fatalf("expected GetAIConversations to be called once, got %d", server.getConversationsCalls)
}
if len(server.getConversationReqs) != 1 {
t.Fatalf("expected GetAIConversation to be called once, got %d", len(server.getConversationReqs))
}
if server.getConversationReqs[0].GetID() != "conv-1" || !server.getConversationReqs[0].GetIncludeMessages() {
t.Fatalf("unexpected GetAIConversation request: %+v", server.getConversationReqs[0])
}
}
func TestLoadAIStateCmdCreatesConversationWhenNoneExist(t *testing.T) {
server := &aiRPCServer{
providersResp: &clientpb.AIProviderConfigs{
Providers: []*clientpb.AIProviderConfig{{Name: "openai", Configured: true}},
Config: &clientpb.AIConfigSummary{
Provider: "openai",
Model: "gpt-test",
Valid: true,
},
},
conversationsResp: &clientpb.AIConversations{},
saveConversationResp: &clientpb.AIConversation{
ID: "conv-created",
Provider: "openai",
Model: "gpt-test",
Title: "New conversation",
},
conversationByID: map[string]*clientpb.AIConversation{
"conv-created": {
ID: "conv-created",
Provider: "openai",
Model: "gpt-test",
Title: "New conversation",
},
},
}
rpcClient, cleanup := newAITestRPCClient(t, server)
defer cleanup()
msg := loadAIStateCmd(&console.SliverClient{Rpc: rpcClient}, "")()
loaded, ok := msg.(aiLoadedMsg)
if !ok {
t.Fatalf("expected aiLoadedMsg, got %T", msg)
}
if loaded.status != "Created a new AI conversation." {
t.Fatalf("unexpected status: %q", loaded.status)
}
if loaded.selectedID != "conv-created" {
t.Fatalf("unexpected selected conversation id: %q", loaded.selectedID)
}
server.mu.Lock()
defer server.mu.Unlock()
if len(server.saveConversationReqs) != 1 {
t.Fatalf("expected SaveAIConversation to be called once, got %d", len(server.saveConversationReqs))
}
request := server.saveConversationReqs[0]
if request.GetProvider() != "openai" || request.GetModel() != "gpt-test" || request.GetTitle() != "New conversation" {
t.Fatalf("unexpected SaveAIConversation request: %+v", request)
}
}
func TestSubmitPromptCmdCreatesConversationAndSavesUserMessage(t *testing.T) {
server := &aiRPCServer{
saveConversationResp: &clientpb.AIConversation{
ID: "conv-created",
Provider: "openai",
Model: "gpt-test",
Title: "Explain the workflow.",
},
saveMessageResp: &clientpb.AIConversationMessage{
ID: "msg-1",
ConversationID: "conv-created",
Role: "user",
Content: "Explain the workflow.\n\nWith details.",
},
}
rpcClient, cleanup := newAITestRPCClient(t, server)
defer cleanup()
msg := submitPromptCmd(
&console.SliverClient{Rpc: rpcClient},
nil,
"openai",
"gpt-test",
"Explain the workflow.\n\nWith details.",
)()
submitted, ok := msg.(aiPromptSubmittedMsg)
if !ok {
t.Fatalf("expected aiPromptSubmittedMsg, got %T", msg)
}
if submitted.conversationID != "conv-created" {
t.Fatalf("unexpected conversation id: %q", submitted.conversationID)
}
server.mu.Lock()
defer server.mu.Unlock()
if len(server.saveConversationReqs) != 1 {
t.Fatalf("expected SaveAIConversation to be called once, got %d", len(server.saveConversationReqs))
}
if server.saveConversationReqs[0].GetTitle() != "Explain the workflow." {
t.Fatalf("unexpected conversation title: %q", server.saveConversationReqs[0].GetTitle())
}
if len(server.saveMessageReqs) != 1 {
t.Fatalf("expected SaveAIConversationMessage to be called once, got %d", len(server.saveMessageReqs))
}
request := server.saveMessageReqs[0]
if request.GetConversationID() != "conv-created" || request.GetProvider() != "openai" || request.GetModel() != "gpt-test" || request.GetRole() != "user" {
t.Fatalf("unexpected SaveAIConversationMessage request: %+v", request)
}
if request.GetContent() != "Explain the workflow.\n\nWith details." {
t.Fatalf("unexpected prompt content: %q", request.GetContent())
}
}
func TestSubmitPromptCmdUsesExistingConversationSettings(t *testing.T) {
server := &aiRPCServer{
saveMessageResp: &clientpb.AIConversationMessage{
ID: "msg-1",
ConversationID: "conv-1",
Role: "user",
Content: "hello",
},
}
rpcClient, cleanup := newAITestRPCClient(t, server)
defer cleanup()
msg := submitPromptCmd(
&console.SliverClient{Rpc: rpcClient},
&clientpb.AIConversation{ID: "conv-1", Provider: "anthropic", Model: "claude-test", Title: "Thread"},
"openai",
"gpt-test",
"hello",
)()
if _, ok := msg.(aiPromptSubmittedMsg); !ok {
t.Fatalf("expected aiPromptSubmittedMsg, got %T", msg)
}
server.mu.Lock()
defer server.mu.Unlock()
if len(server.saveConversationReqs) != 0 {
t.Fatalf("did not expect SaveAIConversation to be called, got %d calls", len(server.saveConversationReqs))
}
if len(server.saveMessageReqs) != 1 {
t.Fatalf("expected SaveAIConversationMessage to be called once, got %d", len(server.saveMessageReqs))
}
request := server.saveMessageReqs[0]
if request.GetProvider() != "anthropic" || request.GetModel() != "claude-test" {
t.Fatalf("unexpected conversation settings in message request: %+v", request)
}
}
func TestDeleteConversationCmdSendsDeleteRequest(t *testing.T) {
server := &aiRPCServer{}
rpcClient, cleanup := newAITestRPCClient(t, server)
defer cleanup()
msg := deleteConversationCmd(
&console.SliverClient{Rpc: rpcClient},
"conv-1",
"conv-2",
`Deleted "Thread".`,
)()
deleted, ok := msg.(aiConversationDeletedMsg)
if !ok {
t.Fatalf("expected aiConversationDeletedMsg, got %T", msg)
}
if deleted.conversationID != "conv-1" || deleted.selectedID != "conv-2" {
t.Fatalf("unexpected delete message: %+v", deleted)
}
server.mu.Lock()
defer server.mu.Unlock()
if len(server.deleteConversationReqs) != 1 {
t.Fatalf("expected DeleteAIConversation to be called once, got %d", len(server.deleteConversationReqs))
}
if server.deleteConversationReqs[0].GetID() != "conv-1" {
t.Fatalf("unexpected DeleteAIConversation request: %+v", server.deleteConversationReqs[0])
}
}
func TestWaitForAIConversationEventCmdFiltersForAIEvents(t *testing.T) {
conversation := &clientpb.AIConversation{ID: "conv-1", OperatorName: "alice"}
data, err := proto.Marshal(conversation)
if err != nil {
t.Fatalf("marshal conversation: %v", err)
}
listener := make(chan *clientpb.Event, 2)
listener <- &clientpb.Event{EventType: consts.WatchtowerEvent}
listener <- &clientpb.Event{EventType: consts.AIConversationEvent, Data: data}
msg := waitForAIConversationEventCmd(listener)()
aiEvent, ok := msg.(aiConversationEventMsg)
if !ok {
t.Fatalf("expected aiConversationEventMsg, got %T", msg)
}
if aiEvent.conversation == nil || aiEvent.conversation.GetID() != "conv-1" {
t.Fatalf("unexpected AI conversation event payload: %+v", aiEvent.conversation)
}
}
func TestWaitForAIConversationEventCmdReturnsClosedMessage(t *testing.T) {
listener := make(chan *clientpb.Event)
close(listener)
msg := waitForAIConversationEventCmd(listener)()
if _, ok := msg.(aiListenerClosedMsg); !ok {
t.Fatalf("expected aiListenerClosedMsg, got %T", msg)
}
}
func TestAIModelReloadsSharedConversationEvents(t *testing.T) {
listener := make(chan *clientpb.Event)
close(listener)
model := newAIModel(nil, aiContext{
connection: aiConnectionSummary{Operator: "alice"},
}, listener)
model.loading = false
updated, cmd := model.Update(aiConversationEventMsg{conversation: &clientpb.AIConversation{
ID: "conv-2",
OperatorName: "bob",
}})
nextModel := updated.(*aiModel)
if !nextModel.loading {
t.Fatal("expected shared conversation event to trigger a reload")
}
if nextModel.status != "Conversation updated on the server. Refreshing..." {
t.Fatalf("unexpected reload status: %q", nextModel.status)
}
if cmd == nil {
t.Fatal("expected shared conversation event to schedule a refresh")
}
}
func TestAIModelReloadsDeleteEventsWhileAwaitingResponse(t *testing.T) {
listener := make(chan *clientpb.Event)
close(listener)
model := newAIModel(nil, aiContext{
connection: aiConnectionSummary{Operator: "alice"},
}, listener)
model.loading = false
model.awaitingResponse = true
model.currentConversation = &clientpb.AIConversation{
ID: "conv-2",
OperatorName: "alice",
UpdatedAt: 100,
Messages: []*clientpb.AIConversationMessage{
{Role: "user", Content: "still waiting"},
},
}
updated, cmd := model.Update(aiConversationEventMsg{conversation: &clientpb.AIConversation{
ID: "conv-2",
OperatorName: "bob",
}})
nextModel := updated.(*aiModel)
if !nextModel.loading {
t.Fatal("expected delete tombstone to reload the conversation")
}
if cmd == nil {
t.Fatal("expected delete tombstone to schedule a refresh")
}
}
type aiRPCServer struct {
rpcpb.UnimplementedSliverRPCServer
mu sync.Mutex
providersResp *clientpb.AIProviderConfigs
conversationsResp *clientpb.AIConversations
conversationByID map[string]*clientpb.AIConversation
saveConversationResp *clientpb.AIConversation
saveMessageResp *clientpb.AIConversationMessage
getProvidersCalls int
getConversationsCalls int
getConversationReqs []*clientpb.AIConversationReq
deleteConversationReqs []*clientpb.AIConversationReq
saveConversationReqs []*clientpb.AIConversation
saveMessageReqs []*clientpb.AIConversationMessage
getConversationStart chan struct{}
getConversationWait <-chan struct{}
saveMessageStarted chan struct{}
saveMessageRelease <-chan struct{}
}
func (s *aiRPCServer) GetAIProviders(context.Context, *commonpb.Empty) (*clientpb.AIProviderConfigs, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.getProvidersCalls++
if s.providersResp == nil {
return &clientpb.AIProviderConfigs{}, nil
}
return proto.Clone(s.providersResp).(*clientpb.AIProviderConfigs), nil
}
func (s *aiRPCServer) GetAIConversations(context.Context, *commonpb.Empty) (*clientpb.AIConversations, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.getConversationsCalls++
if s.conversationsResp == nil {
return &clientpb.AIConversations{}, nil
}
return proto.Clone(s.conversationsResp).(*clientpb.AIConversations), nil
}
func (s *aiRPCServer) GetAIConversation(_ context.Context, req *clientpb.AIConversationReq) (*clientpb.AIConversation, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.getConversationReqs = append(s.getConversationReqs, proto.Clone(req).(*clientpb.AIConversationReq))
if s.getConversationStart != nil {
select {
case s.getConversationStart <- struct{}{}:
default:
}
}
if s.getConversationWait != nil {
s.mu.Unlock()
<-s.getConversationWait
s.mu.Lock()
}
if s.conversationByID == nil {
return &clientpb.AIConversation{}, nil
}
conversation := s.conversationByID[req.GetID()]
if conversation == nil {
return &clientpb.AIConversation{}, nil
}
return proto.Clone(conversation).(*clientpb.AIConversation), nil
}
func (s *aiRPCServer) SaveAIConversation(_ context.Context, req *clientpb.AIConversation) (*clientpb.AIConversation, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.saveConversationReqs = append(s.saveConversationReqs, proto.Clone(req).(*clientpb.AIConversation))
if s.saveConversationResp == nil {
return proto.Clone(req).(*clientpb.AIConversation), nil
}
return proto.Clone(s.saveConversationResp).(*clientpb.AIConversation), nil
}
func (s *aiRPCServer) DeleteAIConversation(_ context.Context, req *clientpb.AIConversationReq) (*commonpb.Empty, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.deleteConversationReqs = append(s.deleteConversationReqs, proto.Clone(req).(*clientpb.AIConversationReq))
return &commonpb.Empty{}, nil
}
func (s *aiRPCServer) SaveAIConversationMessage(_ context.Context, req *clientpb.AIConversationMessage) (*clientpb.AIConversationMessage, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.saveMessageReqs = append(s.saveMessageReqs, proto.Clone(req).(*clientpb.AIConversationMessage))
if s.saveMessageStarted != nil {
select {
case s.saveMessageStarted <- struct{}{}:
default:
}
}
if s.saveMessageRelease != nil {
s.mu.Unlock()
<-s.saveMessageRelease
s.mu.Lock()
}
if s.saveMessageResp == nil {
return proto.Clone(req).(*clientpb.AIConversationMessage), nil
}
return proto.Clone(s.saveMessageResp).(*clientpb.AIConversationMessage), nil
}
func newAITestRPCClient(t *testing.T, srv rpcpb.SliverRPCServer) (rpcpb.SliverRPCClient, func()) {
t.Helper()
listener := bufconn.Listen(1024 * 1024)
grpcServer := grpc.NewServer()
rpcpb.RegisterSliverRPCServer(grpcServer, srv)
go func() {
_ = grpcServer.Serve(listener)
}()
dialer := func(context.Context, string) (net.Conn, error) { return listener.Dial() }
conn, err := grpc.DialContext(context.Background(), "bufnet",
grpc.WithContextDialer(dialer),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
grpcServer.Stop()
_ = listener.Close()
t.Fatalf("dial bufconn: %v", err)
}
cleanup := func() {
_ = conn.Close()
grpcServer.Stop()
_ = listener.Close()
}
return rpcpb.NewSliverRPCClient(conn), cleanup
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+3
View File
@@ -1,6 +1,7 @@
package alias
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
@@ -32,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
aliasCmd.AddCommand(aliasLoadCmd)
flags.NewCompletions(aliasLoadCmd).PositionalCompletion(carapace.ActionDirectories().Tag("alias directory").Usage("path to the alias directory"))
completers.RegisterLocalFilePathPositionalCompletion(aliasLoadCmd, 0)
aliasInstallCmd := &cobra.Command{
Use: consts.InstallStr + " [ALIAS]",
@@ -44,6 +46,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
aliasCmd.AddCommand(aliasInstallCmd)
flags.NewCompletions(aliasInstallCmd).PositionalCompletion(carapace.ActionFiles().Tag("alias file"))
completers.RegisterLocalFilePathPositionalCompletion(aliasInstallCmd, 0)
aliasRemove := &cobra.Command{
Use: consts.RmStr + " [ALIAS]",
+8 -2
View File
@@ -250,7 +250,7 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.StringP("process-arguments", "A", "", "arguments to pass to the hosting process")
f.Uint32P("ppid", "P", 0, "parent process ID to use when creating the hosting process (Windows only)")
f.BoolP("save", "s", false, "Save output to disk")
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
f.Int64P("timeout", "t", defaultTimeout, "command timeout in seconds")
addAliasCmd.Flags().AddFlagSet(f)
// Setup completions for alias arguments
@@ -400,7 +400,13 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
var outFilePath *os.File
if save, _ := cmd.Flags().GetBool("save"); save {
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(cmd.Name()), filepath.Base(session.GetHostname())))
hostname := ""
if session != nil {
hostname = session.GetHostname()
} else if beacon != nil {
hostname = beacon.GetHostname()
}
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", filepath.Base(cmd.Name()), filepath.Base(hostname)))
outFilePath, err = os.CreateTemp("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
+15 -15
View File
@@ -428,7 +428,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Packages" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Packages"))
urlMargin := 150 // Extra margin needed to show URL column
@@ -493,26 +493,26 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
rows := []table.Row{}
for _, pkg := range entries {
color := console.Normal
style := console.StyleNormal
if extensions.CmdExists(pkg.CommandName, sliverMenu.Command) {
color = console.Green
style = console.StyleGreen
}
if con.Settings.SmallTermWidth+urlMargin < width {
rows = append(rows, table.Row{
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
fmt.Sprintf(color+"%s"+console.Normal, pkg.URL),
style.Render(pkg.ArmoryName),
style.Render(pkg.CommandName),
style.Render(pkg.Version),
style.Render(pkg.Type),
style.Render(pkg.Help),
style.Render(pkg.URL),
})
} else {
rows = append(rows, table.Row{
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
fmt.Sprintf(color+"%s"+console.Normal, pkg.Help),
style.Render(pkg.ArmoryName),
style.Render(pkg.CommandName),
style.Render(pkg.Version),
style.Render(pkg.Type),
style.Render(pkg.Help),
})
}
}
@@ -524,7 +524,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Bundles"))
tw.AppendHeader(table.Row{
"Armory Name",
"Name",
+1 -1
View File
@@ -257,7 +257,7 @@ func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdent
if len(extensionUpdates) != 1 {
extensionSuffix = "s"
}
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix)))
tw.AppendHeader(table.Row{
"Package Name",
"Package Type",
+19 -19
View File
@@ -178,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
}
for _, beacon := range beacons {
color := console.Normal
style := console.StyleNormal
activeBeacon := con.ActiveTarget.GetBeacon()
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
color = console.Green
style = console.StyleGreen
}
if beacon.Integrity == "" {
beacon.Integrity = "-"
@@ -192,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
style.Render(beacon.Transport),
style.Render(beacon.RemoteAddress),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
}
if windowsBeaconInList {
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
}
rowEntries = append(rowEntries, []string{
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
style.Render(beacon.Locale),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}...)
} else {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(beacon.Transport),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}
+1 -1
View File
@@ -28,7 +28,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
},
}
flags.Bind("beacons", true, beaconsCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
f.StringP("kill", "k", "", "kill the designated beacon")
+4
View File
@@ -1,6 +1,7 @@
package c2profiles
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
@@ -27,6 +28,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("file", "f", "", "Path to C2 configuration file to import")
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
})
completers.RegisterLocalFilePathFlagCompletion(importC2ProfileCmd, "file")
exportC2ProfileCmd := &cobra.Command{
Use: consts.ExportC2ProfileStr,
@@ -40,6 +42,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
})
completers.RegisterLocalFilePathFlagCompletion(exportC2ProfileCmd, "file")
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
(*comp)["name"] = generate.HTTPC2Completer(con)
})
@@ -76,6 +79,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
})
completers.RegisterLocalFilePathFlagCompletion(generateC2ProfileCmd, "file")
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
(*comp)["template"] = generate.HTTPC2Completer(con)
+12 -13
View File
@@ -2,7 +2,6 @@ package certificates
import (
"context"
"fmt"
"time"
"github.com/bishopfox/sliver/client/command/settings"
@@ -58,28 +57,28 @@ func printCertificateAuthorityInfo(con *console.SliverClient, authData []*client
}
for _, authority := range authData {
rowColor := console.Normal
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
if err == nil {
rowColor = checkCertExpiry(expiry)
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.CreationTime),
rowStyle.Render(authority.Type),
rowStyle.Render(authority.KeyAlgorithm),
rowStyle.Render(authority.ValidityStart),
rowStyle.Render(authority.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.ValidityExpiry),
})
}
}
+17 -18
View File
@@ -2,7 +2,6 @@ package certificates
import (
"context"
"fmt"
"time"
"github.com/bishopfox/sliver/client/command/settings"
@@ -95,22 +94,22 @@ func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []st
printCertificateInfo(con, certificateInfo.Info)
}
func checkCertExpiry(expiryTime time.Time) string {
func checkCertExpiry(expiryTime time.Time) console.TextStyle {
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
return console.Bold + console.Red
return console.StyleBoldRed
}
// One week is 168 hours - this is bad
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
return console.Bold + console.Red
return console.StyleBoldRed
}
// One month is approximately 730 hours - this is a warning
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
return console.Bold + console.Orange
return console.StyleBoldOrange
}
return console.Normal
return console.StyleNormal
}
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
@@ -148,28 +147,28 @@ func printCertificateInfo(con *console.SliverClient, certData []*clientpb.Certif
}
for _, cert := range certData {
rowColor := console.Normal
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
// This should not error out, but if it does, the row will not be colored
if err == nil {
rowColor = checkCertExpiry(expiry)
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.CreationTime),
rowStyle.Render(cert.Type),
rowStyle.Render(cert.KeyAlgorithm),
rowStyle.Render(cert.ValidityStart),
rowStyle.Render(cert.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
rowStyle.Render(cert.ID),
rowStyle.Render(cert.CN),
rowStyle.Render(cert.ValidityExpiry),
})
}
+58
View File
@@ -22,6 +22,7 @@ import (
"net"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// ClientInterfacesCompleter completes interface addresses on the client host.
@@ -84,3 +85,60 @@ func LocalProxyCompleter() carapace.Action {
}).Invoke(c).Prefix(prefix).ToA()
})
}
// LocalFilePathCompleter completes local file and directory paths.
func LocalFilePathCompleter() carapace.Action {
return carapace.ActionFiles().Tag("local file path")
}
// RegisterLocalFilePathFlagCompletion registers local file-path completion for a flag.
func RegisterLocalFilePathFlagCompletion(cmd *cobra.Command, name string) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
})
}
// RegisterLocalFilePathFlagCompletions registers local file-path completion for a list of flags.
func RegisterLocalFilePathFlagCompletions(cmd *cobra.Command, names ...string) {
for _, name := range names {
RegisterLocalFilePathFlagCompletion(cmd, name)
}
}
// RegisterLocalFilePathPositionalCompletion registers local file-path completion for a positional argument index.
func RegisterLocalFilePathPositionalCompletion(cmd *cobra.Command, index int) {
if cmd == nil || index < 0 {
return
}
if cmd.ValidArgsFunction != nil {
return
}
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == index {
return nil, cobra.ShellCompDirectiveDefault
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
// RegisterLocalFilePathPositionalAnyCompletion registers local file-path completion for all positional arguments.
func RegisterLocalFilePathPositionalAnyCompletion(cmd *cobra.Command) {
if cmd == nil {
return
}
if cmd.ValidArgsFunction != nil {
return
}
cmd.ValidArgsFunction = func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}
}
+4
View File
@@ -1,6 +1,7 @@
package crack
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -61,6 +62,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("name", "n", "", "wordlist name (blank = filename)")
})
carapace.Gen(wordlistsAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local wordlist file"))
completers.RegisterLocalFilePathPositionalCompletion(wordlistsAddCmd, 0)
wordlistsCmd.AddCommand(wordlistsAddCmd)
wordlistsRmCmd := &cobra.Command{
@@ -95,6 +97,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("name", "n", "", "rules name (blank = filename)")
})
carapace.Gen(rulesAddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local rules file"))
completers.RegisterLocalFilePathPositionalCompletion(rulesAddCmd, 0)
rulesCmd.AddCommand(rulesAddCmd)
rulesRmCmd := &cobra.Command{
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("name", "n", "", "hcstat2 name (blank = filename)")
})
carapace.Gen(hcstat2AddCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to local hcstat2 file"))
completers.RegisterLocalFilePathPositionalCompletion(hcstat2AddCmd, 0)
hcstat2Cmd.AddCommand(hcstat2AddCmd)
hcstat2RmCmd := &cobra.Command{
+3 -3
View File
@@ -106,17 +106,17 @@ func PrintCrackFiles(crackFiles *clientpb.CrackFiles, con *console.SliverClient)
func PrintCrackFilesByType(crackFiles *clientpb.CrackFiles, con *console.SliverClient) {
wordlistTable := table.NewWriter()
wordlistTable.SetTitle(console.Bold + "Wordlists" + console.Normal)
wordlistTable.SetTitle(console.StyleBold.Render("Wordlists"))
wordlistTable.SetStyle(settings.GetTableStyle(con))
wordlistTable.AppendHeader(table.Row{"Name", "Size"})
rulesTable := table.NewWriter()
rulesTable.SetTitle(console.Bold + "Rules" + console.Normal)
rulesTable.SetTitle(console.StyleBold.Render("Rules"))
rulesTable.SetStyle(settings.GetTableStyle(con))
rulesTable.AppendHeader(table.Row{"Name", "Size"})
hcTable := table.NewWriter()
hcTable.SetTitle(console.Bold + "Markov Hcstat2" + console.Normal)
hcTable.SetTitle(console.StyleBold.Render("Markov Hcstat2"))
hcTable.SetStyle(settings.GetTableStyle(con))
hcTable.AppendHeader(table.Row{"Name", "Size"})
+16 -16
View File
@@ -132,35 +132,35 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient,
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
if 0 < len(cracker.CUDA) {
for _, cuda := range cracker.CUDA {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
}
}
if 0 < len(cracker.Metal) {
for _, metal := range cracker.Metal {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
}
}
if 0 < len(cracker.OpenCL) {
for _, openCL := range cracker.OpenCL {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
}
}
con.Printf("%s\n", tw.Render())
@@ -173,7 +173,7 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
if len(cracker.Benchmarks) == 0 {
+1 -1
View File
@@ -25,7 +25,7 @@ func CrackHcstat2Completer(con *console.SliverClient) carapace.Action {
}
results = append(results, file.Name)
desc := fmt.Sprintf("%s (%s chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
desc := fmt.Sprintf("%s (%d chunks, chunk size: %s)", byteCountSI(file.UncompressedSize), len(file.Chunks), byteCountSI(file.ChunkSize))
results = append(results, desc)
}
+2
View File
@@ -1,6 +1,7 @@
package creds
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
@@ -65,6 +66,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["hash-type"] = CredsHashTypeCompleter(con)
})
carapace.Gen(credsAddFileCmd).PositionalCompletion(carapace.ActionFiles().Tag("credential file"))
completers.RegisterLocalFilePathPositionalCompletion(credsAddFileCmd, 0)
credsAddCmd.AddCommand(credsAddFileCmd)
credsRmCmd := &cobra.Command{
+2 -2
View File
@@ -61,9 +61,9 @@ func printCollection(collection string, creds []*clientpb.Credential, con *conso
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
if collection != "" {
tw.SetTitle(console.Bold + collection + console.Normal)
tw.SetTitle(console.StyleBold.Render(collection))
} else {
tw.SetTitle(console.Bold + "Default Collection" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Default Collection"))
}
tw.AppendHeader(table.Row{
"ID",
+5
View File
@@ -1,6 +1,7 @@
package cursed
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
@@ -75,6 +76,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.BindFlagCompletions(cursedChromeCmd, func(comp *carapace.ActionMap) {
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
})
completers.RegisterLocalFilePathFlagCompletion(cursedChromeCmd, "payload")
cursedChromeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
carapace.Gen(cursedChromeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Chrome CLI arguments"))
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.BindFlagCompletions(cursedEdgeCmd, func(comp *carapace.ActionMap) {
(*comp)["payload"] = carapace.ActionFiles("js").Tag("javascript files")
})
completers.RegisterLocalFilePathFlagCompletion(cursedEdgeCmd, "payload")
cursedEdgeCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
carapace.Gen(cursedEdgeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("additional Edge CLI arguments"))
@@ -130,6 +133,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("", false, CursedCookiesCmd, func(f *pflag.FlagSet) {
f.StringP("save", "s", "", "save to file")
})
completers.RegisterLocalFilePathFlagCompletion(CursedCookiesCmd, "save")
cursedScreenshotCmd := &cobra.Command{
Use: consts.ScreenshotStr,
@@ -144,6 +148,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.Int64P("quality", "q", 100, "screenshot quality (1 - 100)")
f.StringP("save", "s", "", "save to file")
})
completers.RegisterLocalFilePathFlagCompletion(cursedScreenshotCmd, "save")
return []*cobra.Command{cursedCmd}
}
+2 -2
View File
@@ -94,7 +94,7 @@ func CursedChromeCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
}
if chromeExt != nil {
con.Printf("success!\n")
con.PrintInfof("Found viable Chrome extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Found viable Chrome extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -120,7 +120,7 @@ func avadaKedavraChrome(session *clientpb.Session, cmd *cobra.Command, con *cons
if chromeProcess != nil {
con.PrintWarnf("Found running Chrome process: %d (ppid: %d)\n", chromeProcess.GetPid(), chromeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Chrome process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restore existing Chrome process?", &confirm)
+2 -2
View File
@@ -93,7 +93,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
tmpFile, _ := os.CreateTemp("", "cursed")
shell := readline.NewShell()
shell.History.AddFromFile("cursed history", tmpFile.Name())
shell.Prompt.Primary(func() string { return "\033[31mcursed »\033[0m " })
shell.Prompt.Primary(func() string { return console.StyleRed.Render("cursed »") + " " })
// EOFPrompt: "exit",
if con.Settings.VimMode {
@@ -111,7 +111,7 @@ func startCursedConsole(curse *core.CursedProcess, helpers bool, target *overlor
}
}
con.Printf(console.Bold+">>> Cursed Console, use ':help' for options%s\n\n", console.Normal)
con.Printf("%s\n\n", console.StyleBold.Render(">>> Cursed Console, use ':help' for options"))
for {
line, err := shell.Readline()
+2 -2
View File
@@ -77,7 +77,7 @@ func CursedEdgeCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
}
if chromeExt != nil {
con.Printf("success!\n")
con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID)
con.PrintInfof("Found viable Edge extension %s (%s)\n", console.StyleBold.Render(chromeExt.Title), chromeExt.ID)
con.PrintInfof("Injecting payload ... ")
cmd, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse)
// extCtxTimeout, cancel := context.WithTimeout(cmd, 10*time.Second)
@@ -103,7 +103,7 @@ func avadaKedavraEdge(session *clientpb.Session, cmd *cobra.Command, con *consol
if edgeProcess != nil {
con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n")
con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("Sliver will attempt to restore the user's session, however %s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restore existing Edge process?", &confirm)
+1 -1
View File
@@ -85,7 +85,7 @@ func avadaKedavraElectron(electronExe string, session *clientpb.Session, cmd *co
if electronProcess != nil {
con.PrintWarnf("Found running '%s' process: %d (ppid: %d)\n", path.Base(electronExe), electronProcess.GetPid(), electronProcess.GetPpid())
con.PrintWarnf("Sliver will need to kill and restart the process in order to perform code injection.\n")
con.PrintWarnf("%sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal)
con.PrintWarnf("%s\n", console.StyleBold.Render("DATA LOSS MAY OCCUR!"))
con.Printf("\n")
confirm := false
err = forms.Confirm("Kill and restart the process?", &confirm)
+2
View File
@@ -1,6 +1,7 @@
package dllhijack
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["file"] = carapace.ActionFiles()
(*comp)["profile"] = generate.ProfileNameCompleter(con)
})
completers.RegisterLocalFilePathFlagCompletions(dllhijackCmd, "reference-file", "file")
carapace.Gen(dllhijackCmd).PositionalCompletion(carapace.ActionValues().Usage("Path to upload the DLL to on the remote system"))
return []*cobra.Command{dllhijackCmd}
+31
View File
@@ -0,0 +1,31 @@
package docs
import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
// Commands returns the docs command.
func Commands(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{newDocsCommand(consts.SliverCoreHelpGroup, con)}
}
// ServerCommands returns the docs command for the top-level client REPL.
func ServerCommands(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{newDocsCommand(consts.GenericHelpGroup, con)}
}
func newDocsCommand(groupID string, con *console.SliverClient) *cobra.Command {
return &cobra.Command{
Use: consts.DocsStr,
Short: "Browse the embedded Sliver docs in a TUI",
Long: help.GetHelpFor([]string{consts.DocsStr}),
Args: cobra.NoArgs,
GroupID: groupID,
Run: func(cmd *cobra.Command, args []string) {
DocsCmd(cmd, con, args)
},
}
}
+36
View File
@@ -0,0 +1,36 @@
package docs
import (
tea "charm.land/bubbletea/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/charmbracelet/colorprofile"
"github.com/spf13/cobra"
"golang.org/x/term"
)
// DocsCmd launches the embedded docs browser TUI.
func DocsCmd(_ *cobra.Command, con *console.SliverClient, _ []string) {
entries, err := loadDocEntries()
if err != nil {
if con != nil {
con.PrintErrorf("Failed to load embedded docs: %s\n", err)
}
return
}
model := newDocsModel(entries)
width, height := 100, 32
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
width, height = w, h
}
program := tea.NewProgram(
model,
tea.WithWindowSize(width, height),
tea.WithColorProfile(colorprofile.TrueColor),
)
if _, err := program.Run(); err != nil && con != nil {
con.PrintErrorf("Docs TUI error: %s\n", err)
}
}
File diff suppressed because it is too large. Load diff
+199
View File
@@ -0,0 +1,199 @@
package docs
import (
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/ansi"
)
func sampleDocs() []docEntry {
return []docEntry{
{
Name: "Getting Started",
Content: "# Getting Started\n\n- first step\n- second step",
Description: "Getting Started",
},
{
Name: "Operators",
Content: "# Operators\n\nTeam workflows",
Description: "Team workflows",
},
{
Name: "Networking",
Content: "# Networking\n\nTransport details",
Description: "Transport details",
},
}
}
func TestRenderMarkdownWithGlowRendersBulletLists(t *testing.T) {
rendered, err := renderMarkdownWithGlow(48, "# Title\n\n- first item")
if err != nil {
t.Fatalf("renderMarkdownWithGlow(): %v", err)
}
plain := ansi.Strip(rendered)
if !strings.Contains(plain, "Title") {
t.Fatalf("expected heading text, got %q", plain)
}
if !strings.Contains(plain, "• first item") {
t.Fatalf("expected glow-style bullet rendering, got %q", plain)
}
}
func TestSummarizeMarkdownPrefersFirstMeaningfulLine(t *testing.T) {
summary := summarizeMarkdown("# Title\n\n- Bullet\n\nParagraph")
if summary != "Title" {
t.Fatalf("expected first heading summary, got %q", summary)
}
}
func TestSummarizeMarkdownStripsMarkdownAndUnicodeSymbols(t *testing.T) {
summary := summarizeMarkdown("⚠️ **IMPORTANT:** This content was NOT created by the Sliver authors.")
if summary != "IMPORTANT: This content was NOT created by the Sliver authors." {
t.Fatalf("expected sanitized browser summary, got %q", summary)
}
}
func TestDocsModelDefaultsToGettingStarted(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
if model.currentDocName != "Getting Started" {
t.Fatalf("expected Getting Started to be selected, got %q", model.currentDocName)
}
plain := ansi.Strip(model.viewer.View())
if !strings.Contains(plain, "first step") {
t.Fatalf("expected initial viewer content to render selected doc, got %q", plain)
}
}
func TestDocsModelSlashFocusesBrowserAndStartsFiltering(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.focus = docsFocusViewer
updated, cmd := model.Update(tea.KeyPressMsg{Text: "/", Code: '/'})
if cmd == nil {
t.Fatal("expected slash to focus the search input")
}
updatedModel := updated.(*docsModel)
if updatedModel.focus != docsFocusBrowser {
t.Fatalf("expected slash to move focus to browser, got %v", updatedModel.focus)
}
if updatedModel.filterState != docsFilterStateFiltering {
t.Fatalf("expected slash to enter filtering state, got %v", updatedModel.filterState)
}
}
func TestDocsModelFilteringUpdatesVisibleEntries(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.startFiltering()
model.browserFilter.SetValue("oper")
model.updateFilteredEntries(false)
visible := model.visibleEntries()
if len(visible) != 1 || visible[0].Name != "Operators" {
t.Fatalf("expected Operators to be the only filtered result, got %+v", visible)
}
if model.currentDocName != "Operators" {
t.Fatalf("expected filtering to sync selection to Operators, got %q", model.currentDocName)
}
}
func TestDocsModelEnterMovesFocusToViewer(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
updated, _ := model.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
updatedModel := updated.(*docsModel)
if updatedModel.focus != docsFocusViewer {
t.Fatalf("expected enter to focus viewer, got %v", updatedModel.focus)
}
}
func TestDocsModelEscClearsAppliedFilter(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 32)
model.startFiltering()
model.browserFilter.SetValue("net")
model.finishFiltering(false)
updated, _ := model.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
updatedModel := updated.(*docsModel)
if updatedModel.filterState != docsFilterStateUnfiltered {
t.Fatalf("expected esc to clear the filter, got %v", updatedModel.filterState)
}
if len(updatedModel.visibleEntries()) != len(sampleDocs()) {
t.Fatalf("expected filter clear to restore all docs, got %d visible", len(updatedModel.visibleEntries()))
}
}
func TestDocsModelBrowserBodyPinsHelpToBottom(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(120, 28)
body := ansi.Strip(model.renderBrowserBody(model.browserWidth, model.browserHeight))
lines := strings.Split(body, "\n")
if len(lines) != model.browserHeight {
t.Fatalf("expected browser body height %d, got %d lines", model.browserHeight, len(lines))
}
if !strings.Contains(lines[len(lines)-1], "q quit") {
t.Fatalf("expected help controls on the last browser line, got %q", lines[len(lines)-1])
}
}
func TestDocsWindowPollSchedulesResizeAndKeepsPolling(t *testing.T) {
model := newDocsModel(sampleDocs())
model.applyWindowSize(100, 28)
updated, cmd := model.Update(docsWindowPollMsg{width: 120, height: 34})
if cmd == nil {
t.Fatal("expected window poll to schedule resize handling")
}
updatedModel := updated.(*docsModel)
if updatedModel.width != 100 || updatedModel.height != 28 {
t.Fatalf("expected poll tick to leave dimensions unchanged until WindowSizeMsg, got %dx%d", updatedModel.width, updatedModel.height)
}
msg := cmd()
batch, ok := msg.(tea.BatchMsg)
if !ok {
t.Fatalf("expected poll tick to return tea.BatchMsg, got %T", msg)
}
if len(batch) != 2 {
t.Fatalf("expected poll tick batch size 2, got %d", len(batch))
}
var sawWindowSize bool
var sawPollTick bool
for _, subcmd := range batch {
if subcmd == nil {
continue
}
submsg := subcmd()
switch msg := submsg.(type) {
case tea.WindowSizeMsg:
sawWindowSize = true
if msg.Width != 120 || msg.Height != 34 {
t.Fatalf("expected resize 120x34, got %dx%d", msg.Width, msg.Height)
}
case docsWindowPollMsg:
sawPollTick = true
}
}
if !sawWindowSize {
t.Fatal("expected poll tick to emit WindowSizeMsg")
}
if !sawPollTick {
t.Fatal("expected poll tick to schedule another poll")
}
}
+12 -2
View File
@@ -9,6 +9,7 @@ import (
"time"
"unicode/utf8"
tea "charm.land/bubbletea/v2"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/bishopfox/sliver/client/command/filesystem"
@@ -17,8 +18,9 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/colorprofile"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/protobuf/proto"
)
@@ -151,7 +153,15 @@ func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
program := tea.NewProgram(model, tea.WithAltScreen())
width, height := 80, 24
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
width, height = w, h
}
program := tea.NewProgram(
model,
tea.WithWindowSize(width, height),
tea.WithColorProfile(colorprofile.TrueColor),
)
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
+51 -31
View File
@@ -6,11 +6,11 @@ import (
"strings"
"unicode"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/styles"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
"golang.org/x/term"
)
@@ -69,9 +69,12 @@ type editorModel struct {
}
var (
cursorStyle = lipgloss.NewStyle().Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
lineStyle = lipgloss.NewStyle()
)
const (
ansiReverseOn = "\x1b[7m"
ansiReverseOff = "\x1b[0m"
)
func newEditorModel(content, filename string, lexer chroma.Lexer, syntaxName string, showLineNumbers bool) *editorModel {
@@ -108,28 +111,29 @@ func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.height = msg.Height
m.ensureCursorVisible()
return m, nil
case tea.KeyMsg:
case tea.KeyPressMsg:
if m.clearMessage {
m.message = ""
m.clearMessage = false
}
if msg.Type == tea.KeyCtrlC {
key := msg.Key()
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
m.action = actionQuit
return m, tea.Quit
}
switch m.mode {
case modeCommand:
return m.handleCommand(msg)
return m.handleCommand(key)
case modeInsert:
return m.handleInsert(msg)
return m.handleInsert(key)
default:
return m.handleNormal(msg)
return m.handleNormal(key)
}
}
return m, nil
}
func (m *editorModel) View() string {
func (m *editorModel) View() tea.View {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
@@ -152,19 +156,33 @@ func (m *editorModel) View() string {
}
if m.height < 2 {
return b.String()
return editorView(b.String())
}
b.WriteByte('\n')
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
b.WriteString(renderStatusBar(m.width, m.statusLine()))
if m.height < 3 {
return b.String()
return editorView(b.String())
}
b.WriteByte('\n')
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
return b.String()
return editorView(b.String())
}
func renderStatusBar(width int, text string) string {
return ansiReverseOn + lineStyle.Width(width).Render(text) + ansiReverseOff
}
func renderCursorCell(cell string) string {
return ansiReverseOn + cell + ansiReverseOff
}
func editorView(content string) tea.View {
v := tea.NewView(content)
v.AltScreen = true
return v
}
func (m *editorModel) result() editorResult {
@@ -176,8 +194,8 @@ func (m *editorModel) result() editorResult {
}
}
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.mode = modeNormal
m.command = ""
@@ -196,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
}
case tea.KeyBackspace:
m.command = popRune(m.command)
case tea.KeyRunes:
m.command += string(msg.Runes)
default:
if key.Text != "" {
m.command += key.Text
}
}
return m, nil
}
func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleInsert(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.mode = modeNormal
case tea.KeyEnter:
@@ -222,8 +242,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.moveDown()
case tea.KeyTab:
m.insertRune('\t')
case tea.KeyRunes:
for _, r := range msg.Runes {
default:
for _, r := range key.Text {
m.insertRune(r)
}
}
@@ -231,8 +251,8 @@ func (m *editorModel) handleInsert(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.pending = 0
case tea.KeyLeft:
@@ -243,8 +263,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
default:
for _, r := range key.Text {
if m.handleNormalRune(r) {
break
}
@@ -409,9 +429,9 @@ func (m *editorModel) renderLine(index int) string {
if cursor >= start && cursor <= start+contentWidth {
rel := cursor - start
if rel < len(visible) {
lineText = string(visible[:rel]) + cursorStyle.Render(string(visible[rel])) + string(visible[rel+1:])
lineText = string(visible[:rel]) + renderCursorCell(string(visible[rel])) + string(visible[rel+1:])
} else {
lineText = string(visible) + cursorStyle.Render(" ")
lineText = string(visible) + renderCursorCell(" ")
}
}
}
@@ -917,11 +937,11 @@ func (m *editorModel) applyCursor(rendered, highlighted string, line []rune, sta
}
if cursor == len(line) {
left := ansi.Cut(highlighted, start, cursor)
return left + cursorStyle.Render(" ")
return left + renderCursorCell(" ")
}
left := ansi.Cut(highlighted, start, cursor)
right := ansi.Cut(highlighted, cursor+1, end)
return left + cursorStyle.Render(string(line[cursor])) + right
return left + renderCursorCell(string(line[cursor])) + right
}
func (m *editorModel) executeSearch(pattern string) {
+16 -1
View File
@@ -1,6 +1,7 @@
package exec
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
@@ -33,7 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
f.Bool("env-inheritance", false, "inherit environment variables from the current process")
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
f.StringP("name", "n", "", "name to assign loot (optional)")
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
@@ -93,6 +94,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.Gen(executeAssemblyCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to assembly file (required)"))
carapace.Gen(executeAssemblyCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the assembly entrypoint (optional)"))
completers.RegisterLocalFilePathPositionalCompletion(executeAssemblyCmd, 0)
executeShellcodeCmd := &cobra.Command{
Use: consts.ExecuteShellcodeStr,
@@ -112,6 +114,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("shikata-ga-nai", "S", false, "encode shellcode using shikata ga nai prior to execution")
f.StringP("architecture", "A", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
f.Uint32P("iterations", "I", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows PE input only)")
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows PE input only)")
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows PE input only)")
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows PE input only)")
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows PE input only)")
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows PE input only)")
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows PE input only)")
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows PE input only)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
@@ -119,6 +129,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["shikata-ga-nai"] = carapace.ActionValues("386", "amd64").Tag("shikata-ga-nai architectures")
})
carapace.Gen(executeShellcodeCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shellcode file (required)"))
completers.RegisterLocalFilePathPositionalCompletion(executeShellcodeCmd, 0)
sideloadCmd := &cobra.Command{
Use: consts.SideloadStr,
@@ -147,6 +158,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.Gen(sideloadCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to shared library file (required)"))
carapace.Gen(sideloadCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the binary (optional)"))
completers.RegisterLocalFilePathPositionalCompletion(sideloadCmd, 0)
spawnDllCmd := &cobra.Command{
Use: consts.SpawnDllStr,
@@ -175,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.Gen(spawnDllCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to DLL file (required)"))
carapace.Gen(spawnDllCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to pass to the DLL entrypoint (optional)"))
completers.RegisterLocalFilePathPositionalCompletion(spawnDllCmd, 0)
migrateCmd := &cobra.Command{
Use: consts.MigrateStr,
@@ -260,6 +273,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["custom-exe"] = carapace.ActionFiles()
(*comp)["profile"] = generate.ProfileNameCompleter(con)
})
completers.RegisterLocalFilePathFlagCompletion(psExecCmd, "custom-exe")
carapace.Gen(psExecCmd).PositionalCompletion(carapace.ActionValues().Usage("hostname (required)"))
sshCmd := &cobra.Command{
@@ -290,6 +304,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["private-key"] = carapace.ActionFiles()
(*comp)["kerberos-keytab"] = carapace.ActionFiles()
})
completers.RegisterLocalFilePathFlagCompletions(sshCmd, "private-key", "kerberos-keytab")
carapace.Gen(sshCmd).PositionalCompletion(carapace.ActionValues().Usage("remote host to SSH to (required)"))
carapace.Gen(sshCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("command line with arguments"))
+219 -1
View File
@@ -20,27 +20,44 @@ package exec
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/Binject/debug/pe"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
wasmdonut "github.com/sliverarmory/wasm-donut"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/protobuf/proto"
)
const (
defaultExecuteShellcodeDonutEntropy = wasmdonut.DonutEntropyNone
defaultExecuteShellcodeDonutCompress = wasmdonut.DonutCompressNone
defaultExecuteShellcodeDonutExitOpt = wasmdonut.DonutExitThread
defaultExecuteShellcodeDonutBypass = wasmdonut.DonutBypassContinue
defaultExecuteShellcodeDonutHeaders = wasmdonut.DonutHeadersOverwrite
imageFileDLLMask = 0x2000
)
// ExecuteShellcodeCmd - Execute shellcode in-memory.
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
targetName := activeTargetName(session, beacon)
targetOS := activeTargetOS(session, beacon)
targetArch := activeTargetArch(session, beacon)
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
interactive, _ := cmd.Flags().GetBool("interactive")
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
return
}
shellcodeConfig, shellcodeFlagsChanged, err := parseExecuteShellcodeFlags(cmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
shouldConvertPE, isDLLHint := shouldConvertExecuteShellcodePE(shellcodePath, shellcodeFlagsChanged)
if shouldConvertPE {
if targetOS != "windows" {
con.PrintErrorf("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n")
return
}
con.PrintInfof("Converting PE input to shellcode ...\n")
shellcodeBin, err = donutShellcodeFromPE(shellcodeBin, targetArch, isDLLHint, shellcodeConfig)
if err != nil {
con.PrintErrorf("Failed to convert PE input to shellcode: %s\n", err)
return
}
}
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
if shikataGaNai {
if !rwxPages {
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
return
}
ctrl := make(chan bool)
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
msg := fmt.Sprintf("Sending shellcode to %s ...", targetName)
con.SpinUntil(msg, ctrl)
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
Data: shellcodeBin,
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
}
}
func parseExecuteShellcodeFlags(cmd *cobra.Command) (*clientpb.ShellcodeConfig, bool, error) {
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
cmd.Flags().Changed("shellcode-compress") ||
cmd.Flags().Changed("shellcode-exitopt") ||
cmd.Flags().Changed("shellcode-bypass") ||
cmd.Flags().Changed("shellcode-headers") ||
cmd.Flags().Changed("shellcode-thread") ||
cmd.Flags().Changed("shellcode-unicode") ||
cmd.Flags().Changed("shellcode-oep")
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
return nil, false, fmt.Errorf("shellcode-entropy must be between 1 and 3")
}
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
return nil, false, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
}
if shellcodeBypass < 1 || shellcodeBypass > 3 {
return nil, false, fmt.Errorf("shellcode-bypass must be between 1 and 3")
}
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
return nil, false, fmt.Errorf("shellcode-headers must be 1 or 2")
}
shellcodeCompress := uint32(1)
if shellcodeCompressEnabled {
shellcodeCompress = 2
}
return &clientpb.ShellcodeConfig{
Entropy: shellcodeEntropy,
Compress: shellcodeCompress,
ExitOpt: shellcodeExitOpt,
Bypass: shellcodeBypass,
Headers: shellcodeHeaders,
Thread: shellcodeThread,
Unicode: shellcodeUnicode,
OEP: shellcodeOEP,
}, anyChanged, nil
}
func shouldConvertExecuteShellcodePE(shellcodePath string, shellcodeFlagsChanged bool) (bool, bool) {
ext := strings.ToLower(filepath.Ext(shellcodePath))
isPEByExt := ext == ".exe" || ext == ".dll"
return isPEByExt || shellcodeFlagsChanged, ext == ".dll"
}
func activeTargetName(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.GetName()
}
if beacon != nil {
return beacon.GetName()
}
return "target"
}
func activeTargetOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return strings.ToLower(session.GetOS())
}
if beacon != nil {
return strings.ToLower(beacon.GetOS())
}
return ""
}
func activeTargetArch(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.GetArch()
}
if beacon != nil {
return beacon.GetArch()
}
return ""
}
type executeShellcodeDonutOptions struct {
entropy int
compress int
exitOpt int
bypass int
headers int
thread bool
unicode bool
oep uint32
}
func normalizeExecuteShellcodeDonutConfig(config *clientpb.ShellcodeConfig) executeShellcodeDonutOptions {
opts := executeShellcodeDonutOptions{
entropy: defaultExecuteShellcodeDonutEntropy,
compress: defaultExecuteShellcodeDonutCompress,
exitOpt: defaultExecuteShellcodeDonutExitOpt,
bypass: defaultExecuteShellcodeDonutBypass,
headers: defaultExecuteShellcodeDonutHeaders,
}
if config == nil {
return opts
}
if config.Entropy >= 1 && config.Entropy <= 3 {
opts.entropy = int(config.Entropy)
}
if config.Compress >= 1 && config.Compress <= 2 {
opts.compress = int(config.Compress)
}
if config.ExitOpt >= 1 && config.ExitOpt <= 3 {
opts.exitOpt = int(config.ExitOpt)
}
if config.Bypass >= 1 && config.Bypass <= 3 {
opts.bypass = int(config.Bypass)
}
if config.Headers >= 1 && config.Headers <= 2 {
opts.headers = int(config.Headers)
}
opts.thread = config.Thread
opts.unicode = config.Unicode
if config.OEP > 0 {
opts.oep = config.OEP
}
return opts
}
func donutShellcodeFromPE(data []byte, arch string, isDLLHint bool, config *clientpb.ShellcodeConfig) ([]byte, error) {
peFile, err := pe.NewFile(bytes.NewReader(data))
if err != nil {
return nil, err
}
isDLL := isDLLHint || ((peFile.FileHeader.Characteristics & imageFileDLLMask) != 0)
ext := ".exe"
if isDLL {
ext = ".dll"
}
opts := normalizeExecuteShellcodeDonutConfig(config)
result, err := wasmdonut.Generate(context.Background(), data, ext, wasmdonut.GenerateOptions{
Ext: ext,
Arch: getExecuteShellcodeDonutArch(arch),
Bypass: opts.bypass,
Headers: opts.headers,
Entropy: opts.entropy,
Compress: opts.compress,
ExitOpt: opts.exitOpt,
Thread: opts.thread,
Unicode: opts.unicode,
OEP: opts.oep,
})
if err != nil {
return nil, err
}
return addExecuteShellcodeStackCheck(result.Loader), nil
}
func getExecuteShellcodeDonutArch(arch string) int {
donutArch := wasmdonut.DonutArchX84
switch strings.ToLower(arch) {
case "x32", "x86", "386":
donutArch = wasmdonut.DonutArchX86
case "x64", "amd64":
donutArch = wasmdonut.DonutArchX64
case "x84":
donutArch = wasmdonut.DonutArchX84
}
return donutArch
}
func addExecuteShellcodeStackCheck(shellcode []byte) []byte {
stackCheckPrologue := []byte{
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
}
return append(stackCheckPrologue, shellcode...)
}
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
// Check active session
session := con.ActiveTarget.GetSessionInteractive()
@@ -0,0 +1,100 @@
package exec
import (
"testing"
"github.com/spf13/cobra"
)
func TestParseExecuteShellcodeFlagsDefaults(t *testing.T) {
cmd := newExecuteShellcodeFlagsTestCommand(t)
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
if err != nil {
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
}
if changed {
t.Fatalf("expected changed=false with defaults")
}
if cfg == nil {
t.Fatalf("expected non-nil config")
}
if cfg.Entropy != 1 || cfg.Compress != 1 || cfg.ExitOpt != 1 || cfg.Bypass != 3 || cfg.Headers != 1 {
t.Fatalf("unexpected defaults: %+v", cfg)
}
if cfg.Thread || cfg.Unicode || cfg.OEP != 0 {
t.Fatalf("unexpected default bool/OEP values: %+v", cfg)
}
}
func TestParseExecuteShellcodeFlagsChanged(t *testing.T) {
cmd := newExecuteShellcodeFlagsTestCommand(t)
if err := cmd.Flags().Set("shellcode-entropy", "2"); err != nil {
t.Fatalf("set shellcode-entropy: %v", err)
}
if err := cmd.Flags().Set("shellcode-compress", "true"); err != nil {
t.Fatalf("set shellcode-compress: %v", err)
}
if err := cmd.Flags().Set("shellcode-thread", "true"); err != nil {
t.Fatalf("set shellcode-thread: %v", err)
}
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
if err != nil {
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
}
if !changed {
t.Fatalf("expected changed=true")
}
if cfg.Entropy != 2 || cfg.Compress != 2 || !cfg.Thread {
t.Fatalf("unexpected changed config: %+v", cfg)
}
}
func TestParseExecuteShellcodeFlagsValidation(t *testing.T) {
cmd := newExecuteShellcodeFlagsTestCommand(t)
if err := cmd.Flags().Set("shellcode-bypass", "9"); err != nil {
t.Fatalf("set shellcode-bypass: %v", err)
}
if _, _, err := parseExecuteShellcodeFlags(cmd); err == nil {
t.Fatalf("expected validation error for shellcode-bypass")
}
}
func TestShouldConvertExecuteShellcodePE(t *testing.T) {
testCases := []struct {
name string
path string
flagsChanged bool
wantConvert bool
wantDLL bool
}{
{name: "exe by extension", path: "/tmp/payload.exe", flagsChanged: false, wantConvert: true, wantDLL: false},
{name: "dll by extension", path: "/tmp/payload.dll", flagsChanged: false, wantConvert: true, wantDLL: true},
{name: "raw shellcode", path: "/tmp/payload.bin", flagsChanged: false, wantConvert: false, wantDLL: false},
{name: "flags force conversion", path: "/tmp/payload.bin", flagsChanged: true, wantConvert: true, wantDLL: false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotConvert, gotDLL := shouldConvertExecuteShellcodePE(tc.path, tc.flagsChanged)
if gotConvert != tc.wantConvert || gotDLL != tc.wantDLL {
t.Fatalf("unexpected result convert=%v dll=%v, expected convert=%v dll=%v", gotConvert, gotDLL, tc.wantConvert, tc.wantDLL)
}
})
}
}
func newExecuteShellcodeFlagsTestCommand(t *testing.T) *cobra.Command {
t.Helper()
cmd := &cobra.Command{Use: "execute-shellcode"}
cmd.Flags().Uint32("shellcode-entropy", 1, "")
cmd.Flags().Bool("shellcode-compress", false, "")
cmd.Flags().Uint32("shellcode-exitopt", 1, "")
cmd.Flags().Uint32("shellcode-bypass", 3, "")
cmd.Flags().Uint32("shellcode-headers", 1, "")
cmd.Flags().Bool("shellcode-thread", false, "")
cmd.Flags().Bool("shellcode-unicode", false, "")
cmd.Flags().Uint32("shellcode-oep", 0, "")
return cmd
}
+4
View File
@@ -79,10 +79,14 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if token || hidden || ppid != 0 {
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
ctrl <- true
<-ctrl
return
}
if envInheritance || len(envVars) > 0 {
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
ctrl <- true
<-ctrl
return
}
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
+3
View File
@@ -1,6 +1,7 @@
package extensions
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/command/use"
@@ -33,6 +34,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
extensionCmd.AddCommand(extensionLoadCmd)
carapace.Gen(extensionLoadCmd).PositionalCompletion(carapace.ActionDirectories().Usage("path to the extension directory"))
completers.RegisterLocalFilePathPositionalCompletion(extensionLoadCmd, 0)
extensionInstallCmd := &cobra.Command{
Use: consts.InstallStr,
@@ -45,6 +47,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
extensionCmd.AddCommand(extensionInstallCmd)
carapace.Gen(extensionInstallCmd).PositionalCompletion(carapace.ActionFiles().Usage("path to the extension .tar.gz or directory"))
completers.RegisterLocalFilePathPositionalCompletion(extensionInstallCmd, 0)
extensionRmCmd := &cobra.Command{
Use: consts.RmStr,
+14 -8
View File
@@ -3,6 +3,8 @@ package extensions
import (
"encoding/json"
"testing"
"github.com/bishopfox/sliver/util"
)
/*
@@ -114,6 +116,8 @@ const (
)
func TestParseExtensionManifest(t *testing.T) {
expectedPath := util.ResolvePath("foo/test1.dll")
extManifest, err := ParseExtensionManifest([]byte(sample1))
if err != nil {
t.Fatalf("Error parsing extension manifest: %s", err)
@@ -150,8 +154,8 @@ func TestParseExtensionManifest(t *testing.T) {
if extCmd.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extCmd.Files[0].Arch)
}
if extCmd.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extCmd.Files[0].Path)
if extCmd.Files[0].Path != expectedPath {
t.Errorf("Expected path '%s', got '%s'", expectedPath, extCmd.Files[0].Path)
}
}
@@ -178,14 +182,16 @@ func TestParseExtensionManifest(t *testing.T) {
if extManifest2.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
}
if extManifest2.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
if extManifest2.Files[0].Path != expectedPath {
t.Errorf("Expected path '%s', got '%s'", expectedPath, extManifest2.Files[0].Path)
}
}
}
func TestParseMultipleCmdManifest(t *testing.T) {
expectedPath := util.ResolvePath("ex.dll")
mextManifest, err := ParseExtensionManifest([]byte(multicmd))
if err != nil {
t.Errorf("error parsing manifest: %s", err)
@@ -206,11 +212,11 @@ func TestParseMultipleCmdManifest(t *testing.T) {
if mextManifest.ExtCommand[1].Entrypoint != "Test2" {
t.Errorf("expected entrypoint Test2, got %s", mextManifest.ExtCommand[1].Entrypoint)
}
if mextManifest.ExtCommand[0].Files[0].Path != "/ex.dll" { //path cleaning adds a root path here? I am not sure if this should be a bug or not... works fine in prod
t.Errorf("expected path ex.dll, got %s", mextManifest.ExtCommand[0].Files[0].Path)
if mextManifest.ExtCommand[0].Files[0].Path != expectedPath { // path normalization is platform-specific
t.Errorf("expected path %s, got %s", expectedPath, mextManifest.ExtCommand[0].Files[0].Path)
}
if mextManifest.ExtCommand[1].Files[0].Path != "/ex.dll" { //path cleaning adds a root path here? I am not sure if this should be a bug or not... works fine in prod
t.Errorf("expected path ex.dll, got %s", mextManifest.ExtCommand[0].Files[0].Path)
if mextManifest.ExtCommand[1].Files[0].Path != expectedPath { // path normalization is platform-specific
t.Errorf("expected path %s, got %s", expectedPath, mextManifest.ExtCommand[1].Files[0].Path)
}
//maybe some more? args?
}
+1 -1
View File
@@ -377,7 +377,7 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
// Flags
f := pflag.NewFlagSet(extCmd.CommandName, pflag.ContinueOnError)
f.BoolP("save", "s", false, "Save output to disk")
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
f.Int64P("timeout", "t", defaultTimeout, "command timeout in seconds")
extensionCmd.Flags().AddFlagSet(f)
extensionCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
+3
View File
@@ -1,6 +1,7 @@
package filesystem
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/command/loot"
@@ -174,6 +175,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.ActionValues().Usage("path to the file or directory to download"),
carapace.ActionFiles().Usage("local path where the downloaded file will be saved (optional)"),
)
completers.RegisterLocalFilePathPositionalCompletion(downloadCmd, 1)
uploadCmd := &cobra.Command{
Use: consts.UploadStr,
@@ -194,6 +196,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.ActionFiles().Usage("local path to the file to upload"),
carapace.ActionValues().Usage("path to the file or directory to upload to (optional)"),
)
completers.RegisterLocalFilePathPositionalCompletion(uploadCmd, 0)
memfilesCmd := &cobra.Command{
Use: consts.MemfilesStr,
+4 -5
View File
@@ -29,7 +29,6 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/fatih/color"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
@@ -239,19 +238,19 @@ func printGrep(grep *sliverpb.Grep, searchPattern string, searchPath string, cmd
// grepLineResult - Add color or formatting for results for console output
func grepLineResult(positions []*sliverpb.GrepLinePosition, line string, colorize bool, allowFormatting bool) string {
var result string = ""
var matchOutput func(a ...interface{}) string
var previousPositionEnd int32 = 0
matchStyle := console.StyleNormal
if colorize {
matchOutput = color.New(color.FgRed, color.Bold).SprintFunc()
matchStyle = console.StyleBoldRed
} else if allowFormatting {
matchOutput = color.New(color.Bold).SprintFunc()
matchStyle = console.StyleBold
}
for idx, position := range positions {
if colorize || allowFormatting {
result += line[previousPositionEnd:position.Start]
result += matchOutput(line[position.Start:position.End])
result += matchStyle.Render(line[position.Start:position.End])
if idx == len(positions)-1 {
result += line[position.End:]
} else {
+7 -7
View File
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
if burnedOnly && !canary.Triggered {
continue
}
lineColor := console.Normal
lineStyle := console.StyleNormal
if canary.Triggered {
lineColor = console.Bold + console.Red
lineStyle = console.StyleBoldRed
}
firstTrigger := "Never"
latestTrigger := "Never"
if canary.Triggered {
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
firstTrigger = lineStyle.Render(canary.FirstTriggered)
latestTrigger = lineStyle.Render(canary.LatestTrigger)
}
row := table.Row{
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
lineStyle.Render(canary.ImplantName),
lineStyle.Render(canary.Domain),
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
firstTrigger,
latestTrigger,
}
+54 -2
View File
@@ -1,6 +1,9 @@
package generate
import (
"strings"
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
@@ -11,6 +14,15 @@ import (
"github.com/spf13/pflag"
)
const spoofMetadataLongHelp = `
Spoof Metadata:
--spoof-metadata
Use metadata settings from <client app dir>/spoof-metadata.yaml.
--spoof-metadata /path/to/donor.exe
Use metadata from the specified donor file. The donor must match the target format/goos/goarch.
`
// Commands returns the “ command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
// [ Generate ] --------------------------------------------------------------
@@ -24,7 +36,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
GroupID: consts.PayloadsHelpGroup,
}
flags.Bind("generate", true, generateCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
// Session flags and completions.
@@ -83,6 +95,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("skip-tests", "s", false, "skip testing the traffic encoder (not recommended)")
})
carapace.Gen(trafficEncodersAddCmd).PositionalCompletion(carapace.ActionFiles("wasm").Tag("wasm files").Usage("local file path (expects .wasm)"))
completers.RegisterLocalFilePathPositionalCompletion(trafficEncodersAddCmd, 0)
trafficEncodersCmd.AddCommand(trafficEncodersAddCmd)
trafficEncodersRmCmd := &cobra.Command{
@@ -115,6 +128,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.BindFlagCompletions(regenerateCmd, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
})
completers.RegisterLocalFilePathFlagCompletion(regenerateCmd, "save")
carapace.Gen(regenerateCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
// [ Profiles ] --------------------------------------------------------------
@@ -151,6 +165,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
})
completers.RegisterLocalFilePathFlagCompletion(profilesGenerateCmd, "save")
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
profilesCmd.AddCommand(profilesGenerateCmd)
@@ -182,6 +197,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("prepend-size", "p", false, "Prepend stage size")
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
})
completers.RegisterLocalFilePathFlagCompletion(profilesStageCmd, "save")
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
profilesCmd.AddCommand(profilesStageCmd)
@@ -324,6 +340,13 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.Uint32P("key-exchange", "X", DefaultWGKeyExPort, "wg key-exchange port")
f.Uint32P("tcp-comms", "T", DefaultWGNPort, "wg c2 comms port")
f.Bool("include-mtls", false, "force include mtls transport")
f.Bool("include-http", false, "force include http transport")
f.Bool("include-wg", false, "force include wireguard transport")
f.Bool("include-dns", false, "force include dns transport")
f.Bool("include-named-pipe", false, "force include named-pipe transport")
f.Bool("include-tcp-pivot", false, "force include tcp-pivot transport")
f.Bool("all-protocols", false, "force include all transport protocols")
f.BoolP("run-at-load", "R", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
f.BoolP("netgo", "q", false, "force the use of netgo")
@@ -342,6 +365,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("limit-hostname", "z", "", "limit execution to specified hostname")
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
bindSpoofMetadataFlag(f)
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
@@ -384,6 +408,32 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
_ = f.MarkHidden("donut-oep")
})
appendSpoofMetadataLongHelp(cmd)
}
func bindSpoofMetadataFlag(f *pflag.FlagSet) {
if f == nil {
return
}
spoofMetadataFlag := f.VarPF(newSpoofMetadataFlagValue(), spoofMetadataFlagName, "", "spoof executable metadata (optional donor path)")
if spoofMetadataFlag != nil {
spoofMetadataFlag.NoOptDefVal = "true"
}
}
func appendSpoofMetadataLongHelp(cmd *cobra.Command) {
if cmd == nil {
return
}
if strings.Contains(cmd.Long, "--spoof-metadata") {
return
}
trimmed := strings.TrimRight(cmd.Long, "\n")
if trimmed == "" {
cmd.Long = strings.TrimLeft(spoofMetadataLongHelp, "\n")
return
}
cmd.Long = trimmed + spoofMetadataLongHelp
}
// coreImplantFlagCompletions binds completions to flags registered in coreImplantFlags.
@@ -394,11 +444,13 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
(*comp)["arch"] = ArchCompleter(con)
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
(*comp)["format"] = FormatCompleter()
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
(*comp)["save"] = completers.LocalFilePathCompleter().Tag("directory/file to save implant")
(*comp)[spoofMetadataFlagName] = carapace.ActionFiles().Tag("optional donor metadata file")
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
(*comp)["c2profile"] = HTTPC2Completer(con)
})
completers.RegisterLocalFilePathFlagCompletions(cmd, "save", "debug-file", spoofMetadataFlagName)
registerImplantTargetFlagCompletions(cmd, con)
}
+51
View File
@@ -0,0 +1,51 @@
package generate
import (
"testing"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
func TestGenerateSaveFlagCompletionRegistered(t *testing.T) {
cmds := Commands(&console.SliverClient{})
generateCmd := commandByUse(cmds, consts.GenerateStr)
if generateCmd == nil {
t.Fatalf("missing %q command", consts.GenerateStr)
}
assertFlagHasDefaultFileCompletion(t, generateCmd, "save")
beaconCmd := commandByUse(generateCmd.Commands(), consts.BeaconStr)
if beaconCmd == nil {
t.Fatalf("missing %q subcommand", consts.BeaconStr)
}
assertFlagHasDefaultFileCompletion(t, beaconCmd, "save")
}
func assertFlagHasDefaultFileCompletion(t *testing.T, cmd *cobra.Command, flag string) {
t.Helper()
completionFn, ok := cmd.GetFlagCompletionFunc(flag)
if !ok {
t.Fatalf("%q missing %q flag completion func", cmd.Use, flag)
}
values, directive := completionFn(cmd, nil, "")
if len(values) != 0 {
t.Fatalf("%q %q completion should return no explicit values, got %d", cmd.Use, flag, len(values))
}
if directive != cobra.ShellCompDirectiveDefault {
t.Fatalf("%q %q completion directive mismatch: got=%v want=%v", cmd.Use, flag, directive, cobra.ShellCompDirectiveDefault)
}
}
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
for _, cmd := range cmds {
if cmd.Use == use {
return cmd
}
}
return nil
}
+8 -3
View File
@@ -41,8 +41,13 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
if config == nil {
return
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
config.IsBeacon = true
err := parseBeaconFlags(cmd, config)
err = parseBeaconFlags(cmd, config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -52,9 +57,9 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
save, _ = os.Getwd()
}
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
compile(name, config, save, con)
compile(name, config, spoofMetadata, save, con)
} else {
externalBuild(name, config, save, con)
externalBuild(name, config, spoofMetadata, save, con)
}
}
+4 -4
View File
@@ -15,19 +15,19 @@ func GenerateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
con.PrintErrorf("Failed to get compiler information: %s\n", err)
return
}
con.Printf("%sServer:%s %s/%s\n", console.Bold, console.Normal, compiler.GOOS, compiler.GOARCH)
con.Printf("%s %s/%s\n", console.StyleBold.Render("Server:"), compiler.GOOS, compiler.GOARCH)
con.Println()
con.Printf("%sCross Compilers%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Cross Compilers"))
for _, cc := range compiler.CrossCompilers {
con.Printf("%s/%s - %s\n", cc.TargetGOOS, cc.TargetGOARCH, cc.GetCCPath())
}
con.Println()
con.Printf("%sSupported Targets%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Supported Targets"))
for _, target := range compiler.Targets {
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
}
con.Println()
con.Printf("%sDefault Builds Only%s\n", console.Bold, console.Normal)
con.Printf("%s\n", console.StyleBold.Render("Default Builds Only"))
for _, target := range compiler.UnsupportedTargets {
con.Printf("%s/%s - %s\n", target.GOOS, target.GOARCH, nameOfOutputFormat(target.Format))
}
+526 -13
View File
@@ -19,7 +19,9 @@ package generate
*/
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"net"
@@ -33,6 +35,8 @@ import (
"strings"
"time"
"github.com/Binject/debug/pe"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
@@ -90,6 +94,11 @@ var (
ErrNoSelection = errors.New("no selection")
)
const (
spoofMetadataFlagName = "spoof-metadata"
imageFileDLLCharacteristic = 0x2000
)
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
@@ -112,14 +121,19 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if config == nil {
return
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
save, _ := cmd.Flags().GetString("save")
if save == "" {
save, _ = os.Getwd()
}
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
compile(name, config, save, con)
compile(name, config, spoofMetadata, save, con)
} else {
_, err := externalBuild(name, config, save, con)
_, err := externalBuild(name, config, spoofMetadata, save, con)
if err != nil {
switch err {
case ErrNoExternalBuilder:
@@ -469,6 +483,23 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
c2Profile = consts.DefaultC2Profile
}
allProtocols, _ := cmd.Flags().GetBool("all-protocols")
includeMTLS, _ := cmd.Flags().GetBool("include-mtls")
includeHTTP, _ := cmd.Flags().GetBool("include-http")
includeWG, _ := cmd.Flags().GetBool("include-wg")
includeDNS, _ := cmd.Flags().GetBool("include-dns")
includeNamedPipe, _ := cmd.Flags().GetBool("include-named-pipe")
includeTCP, _ := cmd.Flags().GetBool("include-tcp-pivot")
if allProtocols {
includeMTLS = true
includeHTTP = true
includeWG = true
includeDNS = true
includeNamedPipe = true
includeTCP = true
}
// exports if its a shared library
config := &clientpb.ImplantConfig{
@@ -513,11 +544,327 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
DebugFile: debugFile,
HTTPC2ConfigName: c2Profile,
ShellcodeConfig: shellcodeConfig,
IncludeMTLS: includeMTLS,
IncludeHTTP: includeHTTP,
IncludeWG: includeWG,
IncludeDNS: includeDNS,
IncludeNamePipe: includeNamedPipe,
IncludeTCP: includeTCP,
}
return name, config
}
func parseSpoofMetadataFlag(cmd *cobra.Command, config *clientpb.ImplantConfig) (*clientpb.SpoofMetadataConfig, error) {
if cmd == nil {
return nil, nil
}
spoofMetadataFlag := cmd.Flags().Lookup(spoofMetadataFlagName)
if spoofMetadataFlag == nil || !spoofMetadataFlag.Changed {
return nil, nil
}
spoofMetadataValue := ""
if spoofMetadataFlag.Value != nil {
spoofMetadataValue = strings.TrimSpace(spoofMetadataFlag.Value.String())
}
switch strings.ToLower(spoofMetadataValue) {
case "false":
if len(cmd.Flags().Args()) > 0 {
return nil, fmt.Errorf("--%s=false does not accept a donor path argument", spoofMetadataFlagName)
}
return nil, nil
case "", "true":
pathArg, err := spoofMetadataPathArg(cmd)
if err != nil {
return nil, err
}
if pathArg == "" {
return parseSpoofMetadataFromConfigFile()
}
return parseSpoofMetadataFromPath(pathArg, config)
default:
if len(cmd.Flags().Args()) > 0 {
return nil, fmt.Errorf("--%s accepts a donor path from either the flag value or positional argument, not both", spoofMetadataFlagName)
}
return parseSpoofMetadataFromPath(spoofMetadataValue, config)
}
}
func spoofMetadataPathArg(cmd *cobra.Command) (string, error) {
if cmd == nil {
return "", nil
}
switch args := cmd.Flags().Args(); len(args) {
case 0:
return "", nil
case 1:
return strings.TrimSpace(args[0]), nil
default:
return "", fmt.Errorf("--%s accepts at most one donor path argument", spoofMetadataFlagName)
}
}
func parseSpoofMetadataFromConfigFile() (*clientpb.SpoofMetadataConfig, error) {
configPath := assets.SpoofMetadataConfigPath()
config, err := assets.LoadSpoofMetadataConfig()
if err != nil {
return nil, fmt.Errorf("failed to load spoof metadata config %s: %w", configPath, err)
}
spoofMetadata, err := spoofMetadataConfigToProtobuf(config)
if err != nil {
return nil, fmt.Errorf("invalid spoof metadata config %s: %w", configPath, err)
}
return spoofMetadata, nil
}
func parseSpoofMetadataFromPath(pathValue string, config *clientpb.ImplantConfig) (*clientpb.SpoofMetadataConfig, error) {
if config == nil {
return nil, errors.New("missing implant configuration for spoof metadata validation")
}
pathValue = expandPath(pathValue)
sourceData, err := os.ReadFile(pathValue)
if err != nil {
return nil, fmt.Errorf("failed to read spoof metadata source path %s: %w", pathValue, err)
}
if err := validateSpoofMetadataSourceForTarget(config, pathValue, sourceData); err != nil {
return nil, err
}
return &clientpb.SpoofMetadataConfig{
PE: &clientpb.PESpoofMetadataConfig{
Source: &clientpb.SpoofMetadataFile{
Name: filepath.Base(pathValue),
Data: sourceData,
},
},
}, nil
}
func validateSpoofMetadataSourceForTarget(config *clientpb.ImplantConfig, sourcePath string, sourceData []byte) error {
if config == nil {
return errors.New("missing implant configuration for spoof metadata validation")
}
if strings.ToLower(config.GetGOOS()) != "windows" {
return fmt.Errorf("--%s path source currently supports only windows PE targets (got %s/%s)", spoofMetadataFlagName, config.GetGOOS(), config.GetGOARCH())
}
switch config.GetFormat() {
case clientpb.OutputFormat_EXECUTABLE, clientpb.OutputFormat_SERVICE, clientpb.OutputFormat_SHARED_LIB:
default:
return fmt.Errorf("--%s path source requires an executable/service/shared target format (got %s)", spoofMetadataFlagName, nameOfOutputFormat(config.GetFormat()))
}
peFile, err := pe.NewFile(bytes.NewReader(sourceData))
if err != nil {
return fmt.Errorf("spoof metadata source %s is not a valid PE: %w", sourcePath, err)
}
defer peFile.Close()
expectedMachine, err := expectedPEMachineForGoArch(config.GetGOARCH())
if err != nil {
return err
}
if peFile.FileHeader.Machine != expectedMachine {
return fmt.Errorf(
"spoof metadata source %s has machine %s, expected %s for target arch %s",
sourcePath,
peMachineName(peFile.FileHeader.Machine),
peMachineName(expectedMachine),
config.GetGOARCH(),
)
}
isDLL := peFile.FileHeader.Characteristics&imageFileDLLCharacteristic != 0
if config.GetFormat() == clientpb.OutputFormat_SHARED_LIB && !isDLL {
return fmt.Errorf("spoof metadata source %s must be a DLL for shared target format", sourcePath)
}
if (config.GetFormat() == clientpb.OutputFormat_EXECUTABLE || config.GetFormat() == clientpb.OutputFormat_SERVICE) && isDLL {
return fmt.Errorf("spoof metadata source %s appears to be a DLL but target format is %s", sourcePath, nameOfOutputFormat(config.GetFormat()))
}
return nil
}
func expectedPEMachineForGoArch(goarch string) (uint16, error) {
switch strings.ToLower(strings.TrimSpace(goarch)) {
case "amd64", "x64":
return pe.IMAGE_FILE_MACHINE_AMD64, nil
case "386", "x86", "i386":
return pe.IMAGE_FILE_MACHINE_I386, nil
case "arm64", "aarch64":
return pe.IMAGE_FILE_MACHINE_ARM64, nil
default:
return 0, fmt.Errorf("unsupported windows architecture for spoof metadata source validation: %s", goarch)
}
}
func peMachineName(machine uint16) string {
switch machine {
case pe.IMAGE_FILE_MACHINE_AMD64:
return "amd64"
case pe.IMAGE_FILE_MACHINE_I386:
return "386"
case pe.IMAGE_FILE_MACHINE_ARM64:
return "arm64"
default:
return fmt.Sprintf("0x%x", machine)
}
}
func spoofMetadataConfigToProtobuf(config *assets.SpoofMetadataConfig) (*clientpb.SpoofMetadataConfig, error) {
if config == nil {
return nil, errors.New("missing spoof metadata configuration")
}
spoofMetadata := &clientpb.SpoofMetadataConfig{}
if config.PE != nil {
source, err := resolveSpoofMetadataDataSource("pe.source", config.PE.Source)
if err != nil {
return nil, err
}
icon, err := resolveSpoofMetadataDataSource("pe.icon", config.PE.Icon)
if err != nil {
return nil, err
}
spoofMetadata.PE = &clientpb.PESpoofMetadataConfig{
Source: source,
Icon: icon,
ResourceDirectory: imageResourceDirectoryToProtobuf(config.PE.ResourceDirectory),
ResourceDirectoryEntries: imageResourceDirectoryEntriesToProtobuf(config.PE.ResourceDirectoryEntries),
ResourceDataEntries: imageResourceDataEntriesToProtobuf(config.PE.ResourceDataEntries),
ExportDirectory: imageExportDirectoryToProtobuf(config.PE.ExportDirectory),
}
}
if spoofMetadata.GetPE() == nil {
return nil, errors.New("no supported executable metadata configuration found")
}
if spoofMetadata.GetPE().GetSource() == nil || len(spoofMetadata.GetPE().GetSource().GetData()) == 0 {
return nil, errors.New("pe.source requires either path or base64 content")
}
return spoofMetadata, nil
}
func resolveSpoofMetadataDataSource(name string, source *assets.SpoofMetadataDataSource) (*clientpb.SpoofMetadataFile, error) {
if source == nil {
return nil, nil
}
pathValue := strings.TrimSpace(source.Path)
base64Value := strings.TrimSpace(source.Base64)
if pathValue != "" && base64Value != "" {
return nil, fmt.Errorf("%s cannot set both path and base64", name)
}
if pathValue == "" && base64Value == "" {
return nil, nil
}
var (
data []byte
err error
)
fileName := strings.TrimSpace(source.Name)
if pathValue != "" {
pathValue = expandPath(pathValue)
data, err = os.ReadFile(pathValue)
if err != nil {
return nil, fmt.Errorf("%s failed to read path %s: %w", name, pathValue, err)
}
if fileName == "" {
fileName = filepath.Base(pathValue)
}
} else {
data, err = base64.StdEncoding.DecodeString(base64Value)
if err != nil {
data, err = base64.RawStdEncoding.DecodeString(base64Value)
if err != nil {
return nil, fmt.Errorf("%s has invalid base64 data: %w", name, err)
}
}
if fileName == "" {
fileName = "inline-data"
}
}
return &clientpb.SpoofMetadataFile{
Name: fileName,
Data: data,
}, nil
}
func imageResourceDirectoryToProtobuf(src *assets.ImageResourceDirectory) *clientpb.IMAGE_RESOURCE_DIRECTORY {
if src == nil {
return nil
}
return &clientpb.IMAGE_RESOURCE_DIRECTORY{
Characteristics: src.Characteristics,
TimeDateStamp: src.TimeDateStamp,
MajorVersion: src.MajorVersion,
MinorVersion: src.MinorVersion,
NumberOfNamedEntries: src.NumberOfNamedEntries,
NumberOfIdEntries: src.NumberOfIDEntries,
}
}
func imageResourceDirectoryEntriesToProtobuf(src []*assets.ImageResourceDirectoryEntry) []*clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY {
if len(src) == 0 {
return nil
}
out := make([]*clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY, 0, len(src))
for _, entry := range src {
if entry == nil {
continue
}
out = append(out, &clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY{
Name: entry.Name,
OffsetToData: entry.OffsetToData,
})
}
return out
}
func imageResourceDataEntriesToProtobuf(src []*assets.ImageResourceDataEntry) []*clientpb.IMAGE_RESOURCE_DATA_ENTRY {
if len(src) == 0 {
return nil
}
out := make([]*clientpb.IMAGE_RESOURCE_DATA_ENTRY, 0, len(src))
for _, entry := range src {
if entry == nil {
continue
}
out = append(out, &clientpb.IMAGE_RESOURCE_DATA_ENTRY{
OffsetToData: entry.OffsetToData,
Size: entry.Size,
CodePage: entry.CodePage,
Reserved: entry.Reserved,
})
}
return out
}
func imageExportDirectoryToProtobuf(src *assets.ImageExportDirectory) *clientpb.IMAGE_EXPORT_DIRECTORY {
if src == nil {
return nil
}
return &clientpb.IMAGE_EXPORT_DIRECTORY{
Characteristics: src.Characteristics,
TimeDateStamp: src.TimeDateStamp,
MajorVersion: src.MajorVersion,
MinorVersion: src.MinorVersion,
Name: src.Name,
Base: src.Base,
NumberOfFunctions: src.NumberOfFunctions,
NumberOfNames: src.NumberOfNames,
AddressOfFunctions: src.AddressOfFunctions,
AddressOfNames: src.AddressOfNames,
AddressOfNameOrdinals: src.AddressOfNameOrdinals,
}
}
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
@@ -820,9 +1167,7 @@ func getTargets(targetOS string, targetArch string, con *console.SliverClient) (
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
if _, ok := SupportedCompilerTargets[target]; !ok {
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
console.Bold, target, console.Normal,
)
con.Printf("⚠️ Unsupported compiler target %s, but we can try to compile a generic implant.\n", console.StyleBold.Render(target))
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
var confirm bool
_ = forms.Confirm("Attempt to build generic implant?", &confirm)
@@ -1148,7 +1493,150 @@ func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
return c2s, nil
}
func externalBuild(name string, config *clientpb.ImplantConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
func applySpoofMetadataToBuild(implantBuildID string, implantName string, spoofMetadata *clientpb.SpoofMetadataConfig, con *console.SliverClient) error {
if spoofMetadata == nil {
return nil
}
req := &clientpb.GenerateSpoofMetadataReq{
SpoofMetadata: spoofMetadata,
}
if buildID := strings.TrimSpace(implantBuildID); buildID != "" {
req.ImplantBuildID = buildID
} else if buildName := strings.TrimSpace(implantName); buildName != "" {
req.ImplantName = buildName
}
if req.GetImplantBuildID() == "" && req.GetImplantName() == "" {
return errors.New("unable to determine build selector for spoof metadata")
}
printSpoofMetadataSummary(spoofMetadata, con)
con.PrintInfof("Applying spoof metadata to %s ... ", spoofMetadataBuildSelectorDescription(req))
_, err := con.Rpc.GenerateSpoofMetadata(context.Background(), req)
if err != nil {
con.Printf("failed\n")
return err
}
con.Printf("done\n")
return nil
}
func printSpoofMetadataSummary(spoofMetadata *clientpb.SpoofMetadataConfig, con *console.SliverClient) {
if spoofMetadata == nil || con == nil {
return
}
peMetadata := spoofMetadata.GetPE()
if peMetadata == nil {
return
}
sourceName := "<unset>"
sourceSize := 0
if source := peMetadata.GetSource(); source != nil {
sourceName = strings.TrimSpace(source.GetName())
if sourceName == "" {
sourceName = "inline-data"
}
sourceSize = len(source.GetData())
}
iconSummary := "none"
if icon := peMetadata.GetIcon(); icon != nil && len(icon.GetData()) > 0 {
iconName := strings.TrimSpace(icon.GetName())
if iconName == "" {
iconName = "inline-data"
}
iconSummary = fmt.Sprintf("%s (%d bytes)", iconName, len(icon.GetData()))
}
overrideLabels := []string{}
if peMetadata.GetResourceDirectory() != nil {
overrideLabels = append(overrideLabels, "resource-directory")
}
if len(peMetadata.GetResourceDirectoryEntries()) > 0 {
overrideLabels = append(overrideLabels, fmt.Sprintf("resource-entries(%d)", len(peMetadata.GetResourceDirectoryEntries())))
}
if len(peMetadata.GetResourceDataEntries()) > 0 {
overrideLabels = append(overrideLabels, fmt.Sprintf("resource-data-entries(%d)", len(peMetadata.GetResourceDataEntries())))
}
if peMetadata.GetExportDirectory() != nil {
overrideLabels = append(overrideLabels, "export-directory")
}
overridesSummary := "none"
if len(overrideLabels) > 0 {
overridesSummary = strings.Join(overrideLabels, ", ")
}
con.PrintInfof("%s\n", console.StyleBold.Render("Spoof Metadata Details:"))
con.PrintInfof(" Source: %s (%d bytes)\n", sourceName, sourceSize)
con.PrintInfof(" Icon: %s\n", iconSummary)
con.PrintInfof(" Overrides: %s\n", overridesSummary)
}
func spoofMetadataBuildSelectorDescription(req *clientpb.GenerateSpoofMetadataReq) string {
if req == nil {
return "build"
}
if buildID := strings.TrimSpace(req.GetImplantBuildID()); buildID != "" {
return fmt.Sprintf("build id %s", buildID)
}
if buildName := strings.TrimSpace(req.GetImplantName()); buildName != "" {
return fmt.Sprintf("build name %s", buildName)
}
return "build"
}
func requiresWindowsExecutableSuffix(config *clientpb.ImplantConfig) bool {
if config == nil || !strings.EqualFold(config.GetGOOS(), "windows") {
return false
}
switch config.GetFormat() {
case clientpb.OutputFormat_EXECUTABLE, clientpb.OutputFormat_SERVICE:
return true
default:
return false
}
}
func ensureOutputPathSuffix(pathValue string, config *clientpb.ImplantConfig) string {
if pathValue == "" || !requiresWindowsExecutableSuffix(config) {
return pathValue
}
if strings.HasSuffix(strings.ToLower(pathValue), ".exe") {
return pathValue
}
return pathValue + ".exe"
}
func outputSavePath(save string, defaultName string, config *clientpb.ImplantConfig, con *console.SliverClient) (string, error) {
defaultName = ensureOutputPathSuffix(defaultName, config)
saveTo, err := saveLocation(save, defaultName, con)
if err != nil {
return "", err
}
adjustedPath := ensureOutputPathSuffix(saveTo, config)
if adjustedPath == saveTo {
return saveTo, nil
}
if info, err := os.Stat(adjustedPath); err == nil && !info.IsDir() {
var confirm bool
_ = forms.Confirm("Overwrite existing file?", &confirm)
if !confirm {
return "", errors.New("file already exists")
}
} else if err != nil && !os.IsNotExist(err) {
return "", err
}
if con != nil {
con.PrintInfof("Adjusted output filename to %s for Windows executable format\n", adjustedPath)
}
return adjustedPath, nil
}
func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
potentialBuilders, err := findExternalBuilders(config, con)
if err != nil {
return nil, err
@@ -1172,9 +1660,9 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
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)
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
start := time.Now()
@@ -1248,6 +1736,12 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
elapsed := time.Since(start)
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
if spoofMetadata != nil {
if err := applySpoofMetadataToBuild("", name, spoofMetadata, con); err != nil {
return nil, err
}
}
generated, err := con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
ImplantName: name,
})
@@ -1302,7 +1796,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
}
}
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
saveTo, err := outputSavePath(save, filepath.Base(generated.File.Name), config, con)
if err != nil {
return nil, err
}
@@ -1317,7 +1811,7 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
return nil, nil
}
func compile(name string, config *clientpb.ImplantConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
func compile(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
if config.IsBeacon {
interval := time.Duration(config.BeaconInterval)
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
@@ -1325,9 +1819,9 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
con.PrintInfof("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)
con.PrintInfof("%s\n", console.StyleBold.Render("Symbol obfuscation is enabled"))
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %sdisabled%s\n", console.Bold, console.Normal)
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
start := time.Now()
@@ -1345,6 +1839,25 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
return nil, err
}
if spoofMetadata != nil {
if err := applySpoofMetadataToBuild(generated.GetImplantBuildID(), generated.GetImplantName(), spoofMetadata, con); err != nil {
con.PrintErrorf("%s\n", err)
return nil, err
}
implantName := generated.GetImplantName()
if implantName == "" {
implantName = name
}
generated, err = con.Rpc.Regenerate(context.Background(), &clientpb.RegenerateReq{
ImplantName: implantName,
})
if err != nil {
con.PrintErrorf("%s\n", err)
return nil, err
}
}
elapsed := time.Since(start)
con.PrintInfof("Build completed in %s\n", elapsed.Round(time.Second))
if len(generated.File.Data) == 0 {
@@ -1401,7 +1914,7 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
}
}
saveTo, err := saveLocation(save, generated.File.Name, con)
saveTo, err := outputSavePath(save, generated.File.Name, config, con)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -81,7 +81,7 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
}
}
_, err := compile(implantName, profile.Config, save, con)
_, err := compile(implantName, profile.Config, nil, save, con)
if err != nil {
return
}
@@ -0,0 +1,193 @@
package generate
import (
"encoding/base64"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
func TestParseSpoofMetadataFlagDefaultOff(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
config := &clientpb.ImplantConfig{
GOOS: "windows",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
}
if spoofMetadata != nil {
t.Fatal("expected spoof metadata to be nil when flag is not set")
}
}
func TestParseSpoofMetadataFlagNoArgUsesConfig(t *testing.T) {
t.Setenv("SLIVER_CLIENT_ROOT_DIR", t.TempDir())
cmd := newSpoofMetadataTestCommand(t)
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName}); err != nil {
t.Fatalf("ParseFlags() error: %v", err)
}
rootDir := os.Getenv("SLIVER_CLIENT_ROOT_DIR")
configPath := filepath.Join(rootDir, "spoof-metadata.yaml")
sourceData := []byte{0x01, 0x02, 0x03}
configData := "pe:\n source:\n name: donor.exe\n base64: " + base64.StdEncoding.EncodeToString(sourceData) + "\n"
if err := os.WriteFile(configPath, []byte(configData), 0o600); err != nil {
t.Fatalf("write spoof metadata config: %v", err)
}
config := &clientpb.ImplantConfig{
GOOS: "windows",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
}
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
t.Fatal("expected spoof metadata source from config file")
}
if got := spoofMetadata.GetPE().GetSource().GetData(); string(got) != string(sourceData) {
t.Fatalf("source data mismatch: got=%x want=%x", got, sourceData)
}
}
func TestParseSpoofMetadataFlagPathFromPositionalArg(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
if err := cmd.ParseFlags([]string{"--" + spoofMetadataFlagName, sourcePath}); err != nil {
t.Fatalf("ParseFlags() error: %v", err)
}
config := &clientpb.ImplantConfig{
GOOS: "windows",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
}
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
t.Fatal("expected spoof metadata source from positional path")
}
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
}
}
func TestParseSpoofMetadataFlagPathRejectsNonWindowsTarget(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
sourcePath := writeTempFile(t, "not-pe.bin", []byte("abc"))
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
t.Fatalf("set flag: %v", err)
}
config := &clientpb.ImplantConfig{
GOOS: "linux",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
_, err := parseSpoofMetadataFlag(cmd, config)
if err == nil {
t.Fatal("expected error for non-windows target")
}
if !strings.Contains(err.Error(), "supports only windows PE targets") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestParseSpoofMetadataFlagPathChecksMachine(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
sourcePath := goPEFixturePath(t, "gcc-386-mingw-exec")
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
t.Fatalf("set flag: %v", err)
}
config := &clientpb.ImplantConfig{
GOOS: "windows",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
_, err := parseSpoofMetadataFlag(cmd, config)
if err == nil {
t.Fatal("expected machine mismatch error")
}
if !strings.Contains(err.Error(), "expected amd64") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestParseSpoofMetadataFlagPathSuccess(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
sourcePath := goPEFixturePath(t, "gcc-amd64-mingw-exec")
if err := cmd.Flags().Set(spoofMetadataFlagName, sourcePath); err != nil {
t.Fatalf("set flag: %v", err)
}
config := &clientpb.ImplantConfig{
GOOS: "windows",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
}
spoofMetadata, err := parseSpoofMetadataFlag(cmd, config)
if err != nil {
t.Fatalf("parseSpoofMetadataFlag() unexpected error: %v", err)
}
if spoofMetadata == nil || spoofMetadata.GetPE() == nil || spoofMetadata.GetPE().GetSource() == nil {
t.Fatal("expected spoof metadata source from path")
}
if spoofMetadata.GetPE().GetSource().GetName() != filepath.Base(sourcePath) {
t.Fatalf("source name mismatch: got=%q want=%q", spoofMetadata.GetPE().GetSource().GetName(), filepath.Base(sourcePath))
}
if len(spoofMetadata.GetPE().GetSource().GetData()) == 0 {
t.Fatal("expected non-empty source data")
}
}
func TestSpoofMetadataFlagUsageIsClean(t *testing.T) {
cmd := newSpoofMetadataTestCommand(t)
usage := cmd.Flags().FlagUsages()
if !strings.Contains(usage, "--"+spoofMetadataFlagName) {
t.Fatalf("expected usage to include spoof metadata flag, got: %s", usage)
}
if strings.Contains(usage, "string[=") {
t.Fatalf("unexpected optional-string marker in usage: %s", usage)
}
if strings.Contains(usage, `[="`) {
t.Fatalf("unexpected no-opt default rendering in usage: %s", usage)
}
}
func newSpoofMetadataTestCommand(t *testing.T) *cobra.Command {
t.Helper()
cmd := &cobra.Command{Use: "test"}
bindSpoofMetadataFlag(cmd.Flags())
return cmd
}
func writeTempFile(t *testing.T, name string, data []byte) string {
t.Helper()
path := filepath.Join(t.TempDir(), name)
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("write temp file: %v", err)
}
return path
}
func goPEFixturePath(t *testing.T, fixtureName string) string {
t.Helper()
fixturePath := filepath.Join(runtime.GOROOT(), "src", "debug", "pe", "testdata", fixtureName)
if _, err := os.Stat(fixturePath); err != nil {
t.Skipf("missing PE fixture %s: %v", fixturePath, err)
}
return fixturePath
}
@@ -0,0 +1,36 @@
package generate
import "strings"
type spoofMetadataFlagValue struct {
value string
}
func newSpoofMetadataFlagValue() *spoofMetadataFlagValue {
return &spoofMetadataFlagValue{}
}
func (s *spoofMetadataFlagValue) Set(value string) error {
value = strings.TrimSpace(value)
switch strings.ToLower(value) {
case "", "true":
s.value = "true"
case "false":
s.value = "false"
default:
s.value = value
}
return nil
}
func (s *spoofMetadataFlagValue) String() string {
return s.value
}
func (s *spoofMetadataFlagValue) Type() string {
return "bool"
}
func (s *spoofMetadataFlagValue) IsBoolFlag() bool {
return true
}
+4 -4
View File
@@ -71,7 +71,7 @@ func DisplayTrafficEncoders(encoderMap *clientpb.TrafficEncoderMap, con *console
allIDs = append(allIDs, encoder.ID)
name := encoder.Wasm.Name
if isDuplicate {
name = fmt.Sprintf(console.Bold+console.Red+"%s (duplicate id)"+console.Normal, name)
name = console.StyleBoldRed.Render(fmt.Sprintf("%s (duplicate id)", name))
}
tw.AppendRow(table.Row{
encoder.ID,
@@ -212,9 +212,9 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
for _, test := range tests.Tests {
var success string
if test.Success {
success = console.Bold + console.Green + "Passed" + console.Normal
success = console.StyleBoldGreen.Render("Passed")
} else {
success = console.Bold + console.Red + "Failed!" + console.Normal
success = console.StyleBoldRed.Render("Failed!")
}
errorMsg := "N/A"
if test.Err != "" {
@@ -234,7 +234,7 @@ func displayTrafficEncoderTests(running bool, tests *clientpb.TrafficEncoderTest
if running {
con.Println()
con.Println()
con.Printf(console.Bold+" >>> Running test %d of %d please wait ...\r"+console.Normal, len(tests.Tests), tests.TotalTests)
con.Printf("%s\r", console.StyleBold.Render(fmt.Sprintf(" >>> Running test %d of %d please wait ...", len(tests.Tests), tests.TotalTests)))
lineCount += 2
}
+127 -51
View File
@@ -23,12 +23,13 @@ package help
*/
import (
"bytes"
"fmt"
"image/color"
"strings"
"text/template"
"charm.land/lipgloss/v2"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/theme"
)
const (
@@ -55,6 +56,9 @@ var (
consts.LsStr: lsHelp,
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.AIStr: aiHelp,
consts.AIConfigStr: aiConfigHelp,
consts.DocsStr: docsHelp,
consts.CatStr: catHelp,
consts.EditStr: editHelp,
consts.HexEditStr: hexEditHelp,
@@ -166,6 +170,29 @@ _=/usr/bin/env
infoHelp = `[[.Bold]]Command:[[.Normal]] info <sliver name/session>
[[.Bold]]About:[[.Normal]] Get information about a Sliver by name, or for the active Sliver.`
aiHelp = `[[.Bold]]Command:[[.Normal]] ai
[[.Bold]]About:[[.Normal]] Launch the Sliver AI Bubble Tea TUI. The AI workspace now loads server-side conversation threads over gRPC, keeps them synced through the shared event stream, and renders conversation transcripts as markdown in the main pane.
[[.Bold]]Example:[[.Normal]]
use my-session
ai
`
aiConfigHelp = `[[.Bold]]Command:[[.Normal]] ai-config
[[.Bold]]About:[[.Normal]] Open a server-side form to update the AI block in server.yaml. The form stores the default provider, model, thinking level, and provider-specific credentials such as the API key and optional base URL.
[[.Bold]]Example:[[.Normal]]
ai-config
`
docsHelp = `[[.Bold]]Command:[[.Normal]] docs
[[.Bold]]About:[[.Normal]] Browse the embedded Sliver documentation in a Bubble Tea TUI. The left pane is a searchable docs browser, and the right pane renders markdown with Glow styling.
[[.Bold]]Keys:[[.Normal]] Use / to filter docs, enter to focus the selected document, tab to switch panes, and q to quit.
[[.Bold]]Example:[[.Normal]]
docs
`
useHelp = `[[.Bold]]Command:[[.Normal]] use [sliver name/session]
[[.Bold]]About:[[.Normal]] Switch the active Sliver, a valid name must be provided (see sessions).`
@@ -436,8 +463,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode or PE]
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process. On Windows targets, PE input can be
converted with Donut before execution and tuned with [[.Bold]]--shellcode-*[[.Normal]] flags.
[[.Bold]][[.Underline]]++ Shellcode ++[[.Normal]]
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
@@ -1418,24 +1446,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
)
const (
// ANSI Colors
normal = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
orange = "\033[33m"
blue = "\033[34m"
purple = "\033[35m"
cyan = "\033[36m"
gray = "\033[37m"
bold = "\033[1m"
clearln = "\r\x1b[2K"
upN = "\033[%dA"
downN = "\033[%dB"
underline = "\033[4m"
)
// GetHelpFor - Get help string for a command
func GetHelpFor(cmdName []string) string {
if 0 < len(cmdName) {
@@ -1446,34 +1456,100 @@ func GetHelpFor(cmdName []string) string {
return ""
}
// FormatHelpTmpl - Applies format template to help string
type helpStyleState struct {
bold bool
underline bool
fgSet bool
fg color.Color
}
func (s *helpStyleState) reset() {
s.bold = false
s.underline = false
s.fgSet = false
s.fg = nil
}
func (s *helpStyleState) apply(token string) bool {
switch token {
case "Normal":
s.reset()
case "Bold":
s.bold = true
case "Underline":
s.underline = true
case "Black":
s.fgSet, s.fg = true, theme.DefaultMod(50)
case "Red":
s.fgSet, s.fg = true, theme.Danger()
case "Green":
s.fgSet, s.fg = true, theme.Success()
case "Orange":
s.fgSet, s.fg = true, theme.Warning()
case "Blue":
s.fgSet, s.fg = true, theme.Primary()
case "Purple":
s.fgSet, s.fg = true, theme.Secondary()
case "Cyan":
s.fgSet, s.fg = true, theme.PrimaryMod(500)
case "Gray":
// ANSI "gray" (37) is typically light; map to the lightest neutral.
s.fgSet, s.fg = true, theme.DefaultMod(900)
default:
return false
}
return true
}
func (s helpStyleState) render(text string) string {
if text == "" {
return ""
}
st := lipgloss.NewStyle()
if s.bold {
st = st.Bold(true)
}
if s.underline {
st = st.Underline(true)
}
if s.fgSet {
st = st.Foreground(s.fg)
}
return st.Render(text)
}
// FormatHelpTmpl - Applies the help markup (e.g. [[.Bold]]...[[.Normal]]) using lipgloss.
func FormatHelpTmpl(helpStr string) string {
outputBuf := bytes.NewBufferString("")
tmpl, _ := template.New("help").Delims("[[", "]]").Parse(helpStr)
tmpl.Execute(outputBuf, struct {
Normal string
Bold string
Underline string
Black string
Red string
Green string
Orange string
Blue string
Purple string
Cyan string
Gray string
}{
Normal: normal,
Bold: bold,
Underline: underline,
Black: black,
Red: red,
Green: green,
Orange: orange,
Blue: blue,
Purple: purple,
Cyan: cyan,
Gray: gray,
})
return outputBuf.String()
var (
out strings.Builder
state helpStyleState
)
state.reset()
i := 0
for i < len(helpStr) {
j := strings.Index(helpStr[i:], "[[.")
if j < 0 {
out.WriteString(state.render(helpStr[i:]))
break
}
j += i
if j > i {
out.WriteString(state.render(helpStr[i:j]))
}
k := strings.Index(helpStr[j:], "]]")
if k < 0 {
out.WriteString(state.render(helpStr[j:]))
break
}
token := helpStr[j+3 : j+k]
if !state.apply(token) {
// Unknown token; preserve it as plain text.
out.WriteString(helpStr[j : j+k+2])
}
i = j + k + 2
}
return out.String()
}
+62 -43
View File
@@ -5,9 +5,10 @@ import (
"strconv"
"strings"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"golang.org/x/term"
)
@@ -61,13 +62,19 @@ type editorModel struct {
}
var (
cursorStyle = lipgloss.NewStyle().Reverse(true)
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
lineStyle = lipgloss.NewStyle()
)
const (
ansiReverseOn = "\x1b[7m"
ansiReverseOff = "\x1b[0m"
)
func modifiedStyle() lipgloss.Style {
// Read from the global console theme so theme.yaml changes are reflected.
return console.StyleWarning
}
func newEditorModel(data []byte, filename string, offset int) *editorModel {
copyData := append([]byte(nil), data...)
model := &editorModel{
@@ -105,30 +112,31 @@ func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.height = msg.Height
m.ensureCursorVisible()
return m, nil
case tea.KeyMsg:
case tea.KeyPressMsg:
if m.clearMessage {
m.message = ""
m.clearMessage = false
}
if msg.Type == tea.KeyCtrlC {
key := msg.Key()
if key.Mod.Contains(tea.ModCtrl) && key.Code == 'c' {
m.action = actionQuit
return m, tea.Quit
}
switch m.mode {
case modeCommand:
return m.handleCommand(msg)
return m.handleCommand(key)
case modeHexEdit:
return m.handleHexEdit(msg)
return m.handleHexEdit(key)
case modeASCIIEdit:
return m.handleASCIIEdit(msg)
return m.handleASCIIEdit(key)
default:
return m.handleNormal(msg)
return m.handleNormal(key)
}
}
return m, nil
}
func (m *editorModel) View() string {
func (m *editorModel) View() tea.View {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
@@ -151,19 +159,33 @@ func (m *editorModel) View() string {
}
if m.height < 2 {
return b.String()
return hexEditorView(b.String())
}
b.WriteByte('\n')
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
b.WriteString(renderStatusBar(m.width, m.statusLine()))
if m.height < 3 {
return b.String()
return hexEditorView(b.String())
}
b.WriteByte('\n')
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
return b.String()
return hexEditorView(b.String())
}
func renderStatusBar(width int, text string) string {
return ansiReverseOn + lineStyle.Width(width).Render(text) + ansiReverseOff
}
func renderCursorCell(cell string) string {
return ansiReverseOn + cell + ansiReverseOff
}
func hexEditorView(content string) tea.View {
v := tea.NewView(content)
v.AltScreen = true
return v
}
func (m *editorModel) result() editorResult {
@@ -176,8 +198,8 @@ func (m *editorModel) result() editorResult {
}
}
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleCommand(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.mode = modeNormal
m.command = ""
@@ -192,14 +214,16 @@ func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
}
case tea.KeyBackspace:
m.command = popRune(m.command)
case tea.KeyRunes:
m.command += string(msg.Runes)
default:
if key.Text != "" {
m.command += key.Text
}
}
return m, nil
}
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleHexEdit(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.mode = modeNormal
m.nibbleSet = false
@@ -221,8 +245,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case tea.KeyDown:
m.moveDown()
m.nibbleSet = false
case tea.KeyRunes:
for _, r := range msg.Runes {
default:
for _, r := range key.Text {
val, ok := hexNibble(r)
if !ok {
continue
@@ -241,8 +265,8 @@ func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleASCIIEdit(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.mode = modeNormal
case tea.KeyLeft:
@@ -253,8 +277,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
default:
for _, r := range key.Text {
if r < 0 || r > 0xFF {
continue
}
@@ -266,8 +290,8 @@ func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
func (m *editorModel) handleNormal(key tea.Key) (tea.Model, tea.Cmd) {
switch key.Code {
case tea.KeyEsc:
m.pending = 0
case tea.KeyLeft:
@@ -278,8 +302,8 @@ func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
default:
for _, r := range key.Text {
if m.handleNormalRune(r) {
break
}
@@ -399,18 +423,13 @@ func (m *editorModel) renderLine(lineIndex int) string {
asciiRaw := string(printableByte(byteVal))
isModified := m.isModified(idx)
if idx == m.cursor {
if isModified {
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
} else {
hexCells[i] = cursorStyle.Render(hexRaw)
asciiCells[i] = cursorStyle.Render(asciiRaw)
}
hexCells[i] = renderCursorCell(hexRaw)
asciiCells[i] = renderCursorCell(asciiRaw)
continue
}
if isModified {
hexCells[i] = modifiedStyle.Render(hexRaw)
asciiCells[i] = modifiedStyle.Render(asciiRaw)
hexCells[i] = modifiedStyle().Render(hexRaw)
asciiCells[i] = modifiedStyle().Render(asciiRaw)
continue
}
hexCells[i] = hexRaw
+12 -2
View File
@@ -8,6 +8,7 @@ import (
"strings"
"time"
tea "charm.land/bubbletea/v2"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
@@ -15,8 +16,9 @@ import (
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/colorprofile"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/protobuf/proto"
)
@@ -174,7 +176,15 @@ func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
model := newEditorModel(data, filename, offset)
program := tea.NewProgram(model, tea.WithAltScreen())
width, height := 80, 24
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
width, height = w, h
}
program := tea.NewProgram(
model,
tea.WithWindowSize(width, height),
tea.WithColorProfile(colorprofile.TrueColor),
)
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
+38 -38
View File
@@ -70,47 +70,47 @@ func InfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if session != nil {
con.Printf(console.Bold+" Session ID: %s%s\n", console.Normal, session.ID)
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, session.Name)
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, session.Hostname)
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, session.UUID)
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, session.Username)
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, session.UID)
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, session.GID)
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, session.PID)
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, session.OS)
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, session.Version)
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, session.Locale)
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, session.ActiveC2)
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, session.RemoteAddress)
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, session.ProxyURL)
con.Printf(console.Bold+"Reconnect Interval: %s%s\n", console.Normal, time.Duration(session.ReconnectInterval).String())
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Session ID:"), session.ID)
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), session.Name)
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), session.Hostname)
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), session.UUID)
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), session.Username)
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), session.UID)
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), session.GID)
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), session.PID)
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), session.OS)
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), session.Version)
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), session.Locale)
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), session.Arch)
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), session.ActiveC2)
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), session.RemoteAddress)
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), session.ProxyURL)
con.Printf("%s %s\n", console.StyleBold.Render("Reconnect Interval:"), time.Duration(session.ReconnectInterval).String())
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(session.FirstContact, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(session.LastCheckin, 0), true, false))
} else if beacon != nil {
con.Printf(console.Bold+" Beacon ID: %s%s\n", console.Normal, beacon.ID)
con.Printf(console.Bold+" Name: %s%s\n", console.Normal, beacon.Name)
con.Printf(console.Bold+" Hostname: %s%s\n", console.Normal, beacon.Hostname)
con.Printf(console.Bold+" UUID: %s%s\n", console.Normal, beacon.UUID)
con.Printf(console.Bold+" Username: %s%s\n", console.Normal, beacon.Username)
con.Printf(console.Bold+" UID: %s%s\n", console.Normal, beacon.UID)
con.Printf(console.Bold+" GID: %s%s\n", console.Normal, beacon.GID)
con.Printf(console.Bold+" PID: %s%d\n", console.Normal, beacon.PID)
con.Printf(console.Bold+" OS: %s%s\n", console.Normal, beacon.OS)
con.Printf(console.Bold+" Version: %s%s\n", console.Normal, beacon.Version)
con.Printf(console.Bold+" Locale: %s%s\n", console.Normal, beacon.Locale)
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, beacon.Arch)
con.Printf(console.Bold+" Active C2: %s%s\n", console.Normal, beacon.ActiveC2)
con.Printf(console.Bold+" Remote Address: %s%s\n", console.Normal, beacon.RemoteAddress)
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
con.Printf(console.Bold+" Interval: %s%s\n", console.Normal, time.Duration(beacon.Interval).String())
con.Printf(console.Bold+" Jitter: %s%s\n", console.Normal, time.Duration(beacon.Jitter).String())
con.Printf(console.Bold+" First Contact: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
con.Printf(console.Bold+" Last Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
con.Printf(console.Bold+" Next Checkin: %s%s\n", console.Normal, con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
con.Printf("%s %s\n", console.StyleBold.Render(" Beacon ID:"), beacon.ID)
con.Printf("%s %s\n", console.StyleBold.Render(" Name:"), beacon.Name)
con.Printf("%s %s\n", console.StyleBold.Render(" Hostname:"), beacon.Hostname)
con.Printf("%s %s\n", console.StyleBold.Render(" UUID:"), beacon.UUID)
con.Printf("%s %s\n", console.StyleBold.Render(" Username:"), beacon.Username)
con.Printf("%s %s\n", console.StyleBold.Render(" UID:"), beacon.UID)
con.Printf("%s %s\n", console.StyleBold.Render(" GID:"), beacon.GID)
con.Printf("%s %d\n", console.StyleBold.Render(" PID:"), beacon.PID)
con.Printf("%s %s\n", console.StyleBold.Render(" OS:"), beacon.OS)
con.Printf("%s %s\n", console.StyleBold.Render(" Version:"), beacon.Version)
con.Printf("%s %s\n", console.StyleBold.Render(" Locale:"), beacon.Locale)
con.Printf("%s %s\n", console.StyleBold.Render(" Arch:"), beacon.Arch)
con.Printf("%s %s\n", console.StyleBold.Render(" Active C2:"), beacon.ActiveC2)
con.Printf("%s %s\n", console.StyleBold.Render(" Remote Address:"), beacon.RemoteAddress)
con.Printf("%s %s\n", console.StyleBold.Render(" Proxy URL:"), beacon.ProxyURL)
con.Printf("%s %s\n", console.StyleBold.Render(" Interval:"), time.Duration(beacon.Interval).String())
con.Printf("%s %s\n", console.StyleBold.Render(" Jitter:"), time.Duration(beacon.Jitter).String())
con.Printf("%s %s\n", console.StyleBold.Render(" First Contact:"), con.FormatDateDelta(time.Unix(beacon.FirstContact, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Last Checkin:"), con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), true, false))
con.Printf("%s %s\n", console.StyleBold.Render(" Next Checkin:"), con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), true, true))
} else {
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
+29 -2
View File
@@ -1,6 +1,25 @@
package jobs
/*
Sliver Implant Framework
Copyright (C) 2026 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
@@ -24,12 +43,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
GroupID: consts.NetworkHelpGroup,
}
flags.Bind("jobs", true, jobsCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.Bind("jobs", false, jobsCmd, func(f *pflag.FlagSet) {
f.Int32P("kill", "k", -1, "kill a background job")
f.BoolP("kill-all", "K", false, "kill all jobs")
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(jobsCmd, func(comp *carapace.ActionMap) {
(*comp)["kill"] = JobsIDCompleter(con)
@@ -103,6 +122,10 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
})
flags.BindFlagCompletions(httpCmd, func(comp *carapace.ActionMap) {
(*comp)["website"] = WebsiteNameCompleter(con)
})
registerWebsiteFlagCompletion(httpCmd, "website", con)
// HTTPS
httpsCmd := &cobra.Command{
@@ -129,9 +152,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
})
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
(*comp)["website"] = WebsiteNameCompleter(con)
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
})
completers.RegisterLocalFilePathFlagCompletions(httpsCmd, "cert", "key")
registerWebsiteFlagCompletion(httpsCmd, "website", con)
// Staging listeners
stageCmd := &cobra.Command{
@@ -161,6 +187,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["key"] = carapace.ActionFiles().Tag("key file")
(*comp)["compress"] = carapace.ActionValues([]string{"zlib", "gzip", "deflate9", "none"}...).Tag("compression formats")
})
completers.RegisterLocalFilePathFlagCompletions(stageCmd, "cert", "key")
return []*cobra.Command{jobsCmd, mtlsCmd, wgCmd, dnsCmd, httpCmd, httpsCmd, stageCmd}
}
+38
View File
@@ -0,0 +1,38 @@
package jobs
import (
"testing"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
func TestHTTPAndHTTPSWebsiteFlagCompletionRegistered(t *testing.T) {
cmds := Commands(&console.SliverClient{})
httpCmd := commandByUse(cmds, consts.HttpStr)
if httpCmd == nil {
t.Fatalf("missing %q command", consts.HttpStr)
}
if _, ok := httpCmd.GetFlagCompletionFunc("website"); !ok {
t.Fatalf("%q missing website flag completion func", consts.HttpStr)
}
httpsCmd := commandByUse(cmds, consts.HttpsStr)
if httpsCmd == nil {
t.Fatalf("missing %q command", consts.HttpsStr)
}
if _, ok := httpsCmd.GetFlagCompletionFunc("website"); !ok {
t.Fatalf("%q missing website flag completion func", consts.HttpsStr)
}
}
func commandByUse(cmds []*cobra.Command, use string) *cobra.Command {
for _, cmd := range cmds {
if cmd.Use == use {
return cmd
}
}
return nil
}
+100
View File
@@ -0,0 +1,100 @@
package jobs
/*
Sliver Implant Framework
Copyright (C) 2026 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// WebsiteNameCompleter completes the names of available websites.
func WebsiteNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
websites, err := websiteNameValues(con)
if err != nil {
return carapace.ActionMessage("Failed to list websites %s", err)
}
if len(websites) == 0 {
return carapace.ActionMessage("no available websites")
}
return carapace.ActionValues(websites...).Tag("websites").Usage("website name")
})
}
func registerWebsiteFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values, err := websiteNameValues(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func websiteNameValues(con *console.SliverClient) ([]string, error) {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results, nil
}
websites, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, err
}
for _, ws := range websites.Websites {
results = append(results, ws.Name)
}
sort.Strings(results)
return results, nil
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+4 -1
View File
@@ -1,6 +1,7 @@
package loot
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -23,7 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
GroupID: consts.SliverHelpGroup,
}
flags.Bind("loot", true, lootCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.Bind("loot", false, lootCmd, func(f *pflag.FlagSet) {
f.StringP("filter", "f", "", "filter based on loot type")
@@ -50,6 +51,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
carapace.Gen(lootAddCmd).PositionalCompletion(
carapace.ActionFiles().Tag("local loot file").Usage("The local file path to the loot"))
completers.RegisterLocalFilePathPositionalCompletion(lootAddCmd, 0)
lootRemoteCmd := &cobra.Command{
Use: consts.LootRemoteStr,
@@ -99,6 +101,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save loot")
(*comp)["filter"] = FileTypeCompleter(con)
})
completers.RegisterLocalFilePathFlagCompletion(lootFetchCmd, "save")
lootRmCmd := &cobra.Command{
Use: consts.RmStr,
+1 -1
View File
@@ -82,7 +82,7 @@ func PrintLootFile(loot *clientpb.Loot, con *console.SliverClient) {
return
}
if loot.File.Name != "" {
con.PrintInfof("%sFile Name:%s %s\n\n", console.Bold, console.Normal, loot.File.Name)
con.PrintInfof("%s %s\n\n", console.StyleBold.Render("File Name:"), loot.File.Name)
}
if loot.File.Data != nil && 0 < len(loot.File.Data) {
if loot.FileType == clientpb.FileType_TEXT || isText(loot.File.Data) {
+4 -7
View File
@@ -20,7 +20,6 @@ package monitor
import (
"context"
"fmt"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
@@ -99,14 +98,12 @@ func PrintWTConfig(configs *clientpb.MonitoringProviders, con *console.SliverCli
"APIPassword",
})
color := console.Normal
for _, config := range configs.Providers {
row := table.Row{}
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.ID))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.Type))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIKey))
row = append(row, fmt.Sprintf(color+"%s"+console.Normal, config.APIPassword))
row = append(row, config.ID)
row = append(row, config.Type)
row = append(row, config.APIKey)
row = append(row, config.APIPassword)
tw.AppendRow(row)
}
+2 -2
View File
@@ -75,7 +75,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
for index, iface := range interfaces {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableWithBordersStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, iface.Name))
tw.SetTitle(console.StyleBold.Render(iface.Name))
tw.SetColumnConfigs([]table.ColumnConfig{
{Name: "#", AutoMerge: true},
{Name: "IP Address", AutoMerge: true},
@@ -100,7 +100,7 @@ func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverCli
}
}
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
ips = append(ips, console.StyleBoldGreen.Render(ip))
} else if all {
ips = append(ips, fmt.Sprintf("%s", ip))
}
+5 -5
View File
@@ -104,11 +104,11 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
}
if entry.Process != nil && entry.Process.Pid == implantPID {
tw.AppendRow(table.Row{
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
fmt.Sprintf(console.Green+"%s"+console.Normal, srcAddr),
fmt.Sprintf(console.Green+"%s"+console.Normal, dstAddr),
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.SkState),
fmt.Sprintf(console.Green+"%s"+console.Normal, pid),
console.StyleGreen.Render(entry.Protocol),
console.StyleGreen.Render(srcAddr),
console.StyleGreen.Render(dstAddr),
console.StyleGreen.Render(entry.SkState),
console.StyleGreen.Render(pid),
})
} else {
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
+3 -3
View File
@@ -50,7 +50,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
})
for _, operator := range operators {
tw.AppendRow(table.Row{
console.Bold + operator.Name + console.Normal,
console.StyleBold.Render(operator.Name),
status(operator.Online),
})
}
@@ -59,7 +59,7 @@ func displayOperators(operators []*clientpb.Operator, con *console.SliverClient)
func status(isOnline bool) string {
if isOnline {
return console.Bold + console.Green + "Online" + console.Normal
return console.StyleBoldGreen.Render("Online")
}
return console.Bold + console.Red + "Offline" + console.Normal
return console.StyleBoldRed.Render("Offline")
}
+1 -1
View File
@@ -81,7 +81,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
pivotsCmd.AddCommand(pivotDetailsCmd)
flags.Bind("", false, pivotDetailsCmd, func(f *pflag.FlagSet) {
f.IntP("id", "i", 0, "id of the pivot listener to get details for")
f.Uint32P("id", "i", 0, "id of the pivot listener to get details for")
})
flags.BindFlagCompletions(pivotDetailsCmd, func(comp *carapace.ActionMap) {
(*comp)["id"] = PivotIDCompleter(con)
+1 -1
View File
@@ -81,7 +81,7 @@ func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.Sl
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
tw.SetTitle(console.StyleBold.Render(fmt.Sprintf("%s Pivots", PivotTypeToString(listener.Type))))
tw.AppendSeparator()
tw.AppendHeader(table.Row{
"ID",
+1
View File
@@ -39,6 +39,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("", false, addCmd, func(f *pflag.FlagSet) {
f.StringP("remote", "r", "", "remote target host:port (e.g., 10.0.0.1:445)")
f.StringP("bind", "b", "127.0.0.1:8080", "bind port forward to interface")
f.Int32P("keepalive", "k", 0, "keepalive period in seconds (default 30s, -1 to disable)")
})
flags.BindFlagCompletions(addCmd, func(comp *carapace.ActionMap) {
(*comp)["bind"] = completers.ClientInterfacesCompleter()
+11 -1
View File
@@ -68,13 +68,23 @@ func PortfwdAddCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
bindAddr = fmt.Sprintf("127.0.0.1:%s", bindAddr)
}
keepAlive, _ := cmd.Flags().GetInt32("keepalive")
var keepAlivePeriod time.Duration
if keepAlive > 0 {
keepAlivePeriod = time.Duration(keepAlive) * time.Second
} else if keepAlive < 0 {
keepAlivePeriod = -1 * time.Second
} else {
keepAlivePeriod = 30 * time.Second
}
tcpProxy := &tcpproxy.Proxy{}
channelProxy := &core.ChannelProxy{
Rpc: con.Rpc,
Session: session,
RemoteAddr: remoteAddr,
BindAddr: bindAddr,
KeepAlivePeriod: 60 * time.Second,
KeepAlivePeriod: keepAlivePeriod,
DialTimeout: 30 * time.Second,
}
tcpProxy.AddRoute(bindAddr, channelProxy)
+1 -1
View File
@@ -74,7 +74,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
GetSystemCmd(cmd, con, args)
},
GroupID: consts.PrivilegesHelpGroup,
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter, consts.SessionCmdsFilter),
}
flags.Bind("", false, getSystemCmd, func(f *pflag.FlagSet) {
f.StringP("process", "p", "spoolsv.exe", "SYSTEM process to inject into")
+8
View File
@@ -37,6 +37,10 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
if session == nil && beacon == nil {
return
}
if session == nil {
con.PrintErrorf("Command is only supported for sessions.\n")
return
}
targetOS := getOS(session, beacon)
if targetOS != "windows" {
con.PrintErrorf("Command only supported on Windows.\n")
@@ -45,6 +49,10 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
process, _ := cmd.Flags().GetString("process")
config := con.GetActiveSessionConfig()
if config == nil {
con.PrintErrorf("Failed to derive active session config.\n")
return
}
/* If the HTTP C2 Config name is not defined, then put in the default value
This will have no effect on implants that do not use HTTP C2
+3 -1
View File
@@ -1,6 +1,7 @@
package processes
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -51,11 +52,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("loot", "X", false, "save output as loot")
f.StringP("loot-name", "N", "", "name to assign when adding the memory dump to the loot store (optional)")
f.Int32P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(procdumpCmd, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionFiles()
})
completers.RegisterLocalFilePathFlagCompletion(procdumpCmd, "save")
terminateCmd := &cobra.Command{
Use: consts.TerminateStr,
+2 -2
View File
@@ -55,7 +55,7 @@ func ProcdumpCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
return
}
timeout, _ := cmd.Flags().GetInt32("timeout")
timeout, _ := cmd.Flags().GetInt64("timeout")
if timeout < 1 {
con.PrintErrorf("Invalid timeout argument\n")
@@ -68,7 +68,7 @@ func ProcdumpCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
dump, err := con.Rpc.ProcessDump(context.Background(), &sliverpb.ProcessDumpReq{
Request: con.ActiveTarget.Request(cmd),
Pid: int32(pid),
Timeout: timeout,
Timeout: int32(timeout),
})
ctrl <- true
<-ctrl
+107 -107
View File
@@ -36,77 +36,77 @@ import (
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
// Stylizes known processes in the `ps` command
var knownSecurityTools = map[string][]string{
// Process Name -> [Color, Stylized Name]
"ccSvcHst.exe": {console.Red, "Symantec Endpoint Protection"}, // Symantec Endpoint Protection (SEP)
"cb.exe": {console.Red, "Carbon Black"}, // Carbon Black
"RepMgr.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepUtils.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepUx.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"RepWSC.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"scanhost.exe": {console.Red, "Carbon Black Cloud Sensor"}, // Carbon Black Cloud Sensor
"elastic-agent.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
"elastic-endpoint.exe": {console.Red, "Elastic Agent"}, // Elastic Agent
"filebeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - log shipper
"metricbeat.exe": {console.Red, "Elastic Agent"}, // Elastic Agent - metric shipper
"smartscreen.exe": {console.Red, "Windows Smart Screen"}, // Windows Defender Smart Screen
"MpCmdRun.exe": {console.Red, "Windows Defender"}, // Windows Defender Command-line
"MonitoringHost.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
"HealthService.exe": {console.Red, "Windows Defender"}, // Microsoft Monitoring Agent
"MsMpEng.exe": {console.Red, "Windows Defender"}, // Windows Defender (Service Executable)
"NisSrv.exe": {console.Red, "Windows Defender"}, // Windows Defender (Network Realtime Inspection)
"SenseIR.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Live Response Session)
"SenseNdr.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Network Detection and Response)
"SenseSC.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Screenshot Capture Module)
"SenseCE.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Classification Engine Module)
"SenseCM.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Configuration Management Module)
"SenseSampleUploader.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Sample Uploader Module)
"SenseCncProxy.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Communication Module)
"MsSense.exe": {console.Red, "Windows Defender MDE"}, // Windows Defender Endpoint (Service Executable)
"CSFalconService.exe": {console.Red, "CrowdStrike"}, // Crowdstrike Falcon Service
"CSFalconContainer.exe": {console.Red, "CrowdStrike"}, // CrowdStrike Falcon Container Security
"bdservicehost.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
"bdagent.exe": {console.Red, "Bitdefender"}, // Bitdefender (Total Security)
"bdredline.exe": {console.Red, "Bitdefender"}, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
"Deep Security Manager.exe": {console.Red, "Trend Micro"}, // TM Deep Security Manager
"coreServiceShell.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
"ds_monitor.exe": {console.Red, "Trend Micro"}, // TM Deep Security Monitor
"Notifier.exe": {console.Red, "Trend Micro"}, // TM Deep Security Notifier's process
"dsa.exe": {console.Red, "Trend Micro"}, // TM Agent's main process
"ds_nuagent.exe": {console.Red, "Trend Micro"}, // TM Advanced TLS traffic inspection
"coreFrameworkHost.exe": {console.Red, "Trend Micro"}, // TM Anti-malware scan process
"SentinelServiceHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelStaticEngine.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelStaticEngineScanner.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelAgent.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelAgentWorker.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelHelperService.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelBrowserNativeHost.exe": {console.Red, "SentinelOne"}, // Sentinel One
"SentinelUI.exe": {console.Red, "SentinelOne"}, // Sentinel One
"Sysmon.exe": {console.Red, "Sysmon"}, // Sysmon
"Sysmon64.exe": {console.Red, "Sysmon64"}, // Sysmon64
"CylanceSvc.exe": {console.Red, "Cylance"}, // Cylance
"CylanceUI.exe": {console.Red, "Cylance"}, // Cylance
"TaniumClient.exe": {console.Red, "Tanium"}, // Tanium
"TaniumCX.exe": {console.Red, "Tanium"}, // Tanium
"TaniumDetectEngine.exe": {console.Red, "Tanium"}, // Tanium
"collector.exe": {console.Red, "Rapid 7 Collector"}, // Rapid 7 Insight Platform Collector
"ir_agent.exe": {console.Red, "Rapid 7 Insight Agent"}, // Rapid 7 Insight Agent
"eguiproxy.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"ekrn.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"efwd.exe": {console.Red, "ESET Security"}, // ESET Internet Security
"AmSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
// Known security tools shown by `ps`/`pstree`.
// Process executable -> product name.
var knownSecurityTools = map[string]string{
"ccSvcHst.exe": "Symantec Endpoint Protection", // Symantec Endpoint Protection (SEP)
"cb.exe": "Carbon Black", // Carbon Black
"RepMgr.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepUtils.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepUx.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"RepWSC.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"scanhost.exe": "Carbon Black Cloud Sensor", // Carbon Black Cloud Sensor
"elastic-agent.exe": "Elastic Agent", // Elastic Agent
"elastic-endpoint.exe": "Elastic Agent", // Elastic Agent
"filebeat.exe": "Elastic Agent", // Elastic Agent - log shipper
"metricbeat.exe": "Elastic Agent", // Elastic Agent - metric shipper
"smartscreen.exe": "Windows Smart Screen", // Windows Defender Smart Screen
"MpCmdRun.exe": "Windows Defender", // Windows Defender Command-line
"MonitoringHost.exe": "Windows Defender", // Microsoft Monitoring Agent
"HealthService.exe": "Windows Defender", // Microsoft Monitoring Agent
"MsMpEng.exe": "Windows Defender", // Windows Defender (Service Executable)
"NisSrv.exe": "Windows Defender", // Windows Defender (Network Realtime Inspection)
"SenseIR.exe": "Windows Defender MDE", // Windows Defender Endpoint (Live Response Session)
"SenseNdr.exe": "Windows Defender MDE", // Windows Defender Endpoint (Network Detection and Response)
"SenseSC.exe": "Windows Defender MDE", // Windows Defender Endpoint (Screenshot Capture Module)
"SenseCE.exe": "Windows Defender MDE", // Windows Defender Endpoint (Classification Engine Module)
"SenseCM.exe": "Windows Defender MDE", // Windows Defender Endpoint (Configuration Management Module)
"SenseSampleUploader.exe": "Windows Defender MDE", // Windows Defender Endpoint (Sample Uploader Module)
"SenseCncProxy.exe": "Windows Defender MDE", // Windows Defender Endpoint (Communication Module)
"MsSense.exe": "Windows Defender MDE", // Windows Defender Endpoint (Service Executable)
"CSFalconService.exe": "CrowdStrike", // Crowdstrike Falcon Service
"CSFalconContainer.exe": "CrowdStrike", // CrowdStrike Falcon Container Security
"bdservicehost.exe": "Bitdefender", // Bitdefender (Total Security)
"bdagent.exe": "Bitdefender", // Bitdefender (Total Security)
"bdredline.exe": "Bitdefender", // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
"Deep Security Manager.exe": "Trend Micro", // TM Deep Security Manager
"coreServiceShell.exe": "Trend Micro", // TM Anti-malware scan process
"ds_monitor.exe": "Trend Micro", // TM Deep Security Monitor
"Notifier.exe": "Trend Micro", // TM Deep Security Notifier's process
"dsa.exe": "Trend Micro", // TM Agent's main process
"ds_nuagent.exe": "Trend Micro", // TM Advanced TLS traffic inspection
"coreFrameworkHost.exe": "Trend Micro", // TM Anti-malware scan process
"SentinelServiceHost.exe": "SentinelOne", // Sentinel One
"SentinelStaticEngine.exe": "SentinelOne", // Sentinel One
"SentinelStaticEngineScanner.exe": "SentinelOne", // Sentinel One
"SentinelAgent.exe": "SentinelOne", // Sentinel One
"SentinelAgentWorker.exe": "SentinelOne", // Sentinel One
"SentinelHelperService.exe": "SentinelOne", // Sentinel One
"SentinelBrowserNativeHost.exe": "SentinelOne", // Sentinel One
"SentinelUI.exe": "SentinelOne", // Sentinel One
"Sysmon.exe": "Sysmon", // Sysmon
"Sysmon64.exe": "Sysmon64", // Sysmon64
"CylanceSvc.exe": "Cylance", // Cylance
"CylanceUI.exe": "Cylance", // Cylance
"TaniumClient.exe": "Tanium", // Tanium
"TaniumCX.exe": "Tanium", // Tanium
"TaniumDetectEngine.exe": "Tanium", // Tanium
"collector.exe": "Rapid 7 Collector", // Rapid 7 Insight Platform Collector
"ir_agent.exe": "Rapid 7 Insight Agent", // Rapid 7 Insight Agent
"eguiproxy.exe": "ESET Security", // ESET Internet Security
"ekrn.exe": "ESET Security", // ESET Internet Security
"efwd.exe": "ESET Security", // ESET Internet Security
"AmSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CrAmTray.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CrsSvc.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"CybereasonAV.exe": "Cybereason ActiveProbe", // Cybereason ActiveProbe
"cortex-xdr-payload.exe": "Palo Alto Cortex", // Cortex XDR - offline triage
"cysandbox.exe": "Palo Alto Cortex", // Cortex XDR - sandbox
"cyuserservice.exe": "Palo Alto Cortex", // Cortex XDR - user service
"cywscsvc.exe": "Palo Alto Cortex", // Cortex XDR - security center service
"tlaworker.exe": "Palo Alto Cortex", // Cortex XDR - local analysis worker
"AEEngine.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - security service
"Antiexecutable.exe": "Faronics Anti-Executable", // Faronics Anti-Executable - gui and tray icon
}
// PsCmd - List processes on the remote system
@@ -264,10 +264,10 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags
}
func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
uniqProducts := map[string]string{}
uniqProducts := map[string]struct{}{}
for _, proc := range ps.Processes {
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
uniqProducts[secTool[1]] = secTool[0]
if product, ok := knownSecurityTools[proc.Executable]; ok {
uniqProducts[product] = struct{}{}
}
}
products := make([]string, 0, len(uniqProducts))
@@ -281,15 +281,15 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
session, beacon := con.ActiveTarget.GetInteractive()
color := console.Normal
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
color = secTool[0]
style := console.StyleNormal
if _, ok := knownSecurityTools[proc.Executable]; ok {
style = console.StyleRed
}
if session != nil && proc.Pid == session.PID {
color = console.Green
style = console.StyleGreen
}
if beacon != nil && proc.Pid == beacon.PID {
color = console.Green
style = console.StyleGreen
}
var row table.Row
@@ -303,28 +303,28 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
args = proc.Executable
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, args),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(args),
style.Render(fmt.Sprintf("%d", proc.SessionID)),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(proc.Executable),
style.Render(fmt.Sprintf("%d", proc.SessionID)),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Executable),
}
}
}
@@ -341,26 +341,26 @@ func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.S
args = proc.Executable
}
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, args),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(args),
}
} else {
if fullInfo {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Owner),
style.Render(proc.Architecture),
style.Render(proc.Executable),
}
} else {
row = table.Row{
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
style.Render(fmt.Sprintf("%d", proc.Pid)),
style.Render(fmt.Sprintf("%d", proc.Ppid)),
style.Render(proc.Executable),
}
}
}
+5 -6
View File
@@ -1,7 +1,6 @@
package processes
import (
"fmt"
"sort"
"github.com/xlab/treeprint"
@@ -108,14 +107,14 @@ func (t *PsTree) AddProcess(proc *commonpb.Process) {
}
func (t *PsTree) filterProc(proc *commonpb.Process) string {
color := console.Normal
style := console.StyleNormal
if proc.Pid == t.implantPID {
color = console.Green
style = console.StyleGreen
}
if secTool, ok := knownSecurityTools[proc.Executable]; ok {
color = secTool[0]
if _, ok := knownSecurityTools[proc.Executable]; ok {
style = console.StyleRed
}
return fmt.Sprintf(color+"%s"+console.Normal, proc.Executable)
return style.Render(proc.Executable)
}
func (t *PsTree) String() string {
+1 -2
View File
@@ -19,7 +19,6 @@ package reaction
*/
import (
"fmt"
"strings"
"github.com/bishopfox/sliver/client/command/settings"
@@ -51,7 +50,7 @@ func ReactionCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, EventTypeToTitle(eventType)))
tw.SetTitle(console.StyleBold.Render(EventTypeToTitle(eventType)))
tw.AppendSeparator()
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
if slackSpace < 0 {
+1
View File
@@ -26,6 +26,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("beacon-interval", "i", "", "beacon callback interval")
f.StringP("beacon-jitter", "j", "", "beacon callback jitter (random up to)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.StringP("c2-uri", "c", "", "C2 URI to switch to")
})
renameCmd := &cobra.Command{
+48
View File
@@ -20,9 +20,11 @@ package reconfig
import (
"context"
"net/url"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
@@ -52,6 +54,7 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var beaconJitter time.Duration
binterval, _ := cmd.Flags().GetString("beacon-interval")
bjitter, _ := cmd.Flags().GetString("beacon-jitter")
C2URI, _ := cmd.Flags().GetString("c2-uri")
if beacon != nil {
if binterval != "" {
@@ -73,10 +76,55 @@ func ReconfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
if C2URI != "" {
// validate uri
newURI, err := url.Parse(C2URI)
if err != nil || newURI.Scheme == "" || newURI.Host == "" {
con.PrintErrorf("Invalid C2 URI: %s (expected format: protocol://host:port)\n", C2URI)
return
}
// validate the protocol is the same as the beacon (cuz it compiled with only one protocol)
var activeC2 string
if beacon != nil {
activeC2 = beacon.ActiveC2
} else if session != nil {
activeC2 = session.ActiveC2
}
if activeC2 != "" {
currentURI, err := url.Parse(activeC2)
if err == nil {
if newURI.Scheme != currentURI.Scheme {
confirm := false
con.PrintWarnf("Switching protocol from %s to %s. This will only work if the protocol was included in the compiled implant. If it was NOT included, YOU WILL LOSE THE BEACON CONNECTION PERMANENTLY.\n",
currentURI.Scheme, newURI.Scheme)
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
}
// Warning: Switching to a different host typically fails because the implant and server must share the same crypto keys.
if newURI.Hostname() != currentURI.Hostname() {
con.PrintWarnf("Switching to a different host (%s -> %s). This only works if both servers share the same crypto keys.\n",
currentURI.Hostname(), newURI.Hostname())
confirm := false
_ = forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
}
}
}
con.PrintInfof("Updating C2 endpoint to: %s\n", C2URI)
}
reconfig, err := con.Rpc.Reconfigure(context.Background(), &sliverpb.ReconfigureReq{
ReconnectInterval: int64(reconnectInterval),
BeaconInterval: int64(beaconInterval),
BeaconJitter: int64(beaconJitter),
C2URI: C2URI,
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
+3 -1
View File
@@ -1,6 +1,7 @@
package registry
import (
"github.com/bishopfox/sliver/client/command/completers"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -21,7 +22,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
}
flags.Bind("registry", true, registryCmd, func(f *pflag.FlagSet) {
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
registryReadCmd := &cobra.Command{
@@ -56,6 +57,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("name", "n", "", "name to assign loot (optional)")
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
})
completers.RegisterLocalFilePathFlagCompletion(registryReadHiveCmd, "save")
registryReadCmd.AddCommand(registryReadHiveCmd)
registryWriteCmd := &cobra.Command{
+1
View File
@@ -39,6 +39,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("", false, rportfwdAddCmd, func(f *pflag.FlagSet) {
f.StringP("remote", "r", "", "remote address <ip>:<port> connection is forwarded to")
f.StringP("bind", "b", "", "bind address <ip>:<port> for implants to listen on")
f.Int32P("keepalive", "k", 0, "keepalive period in seconds (default 30s, -1 to disable)")
})
flags.BindFlagCompletions(rportfwdAddCmd, func(comp *carapace.ActionMap) {
(*comp)["remote"] = completers.ClientInterfacesCompleter()
Loaded 100 of 2108 files, more files were not shown because too many files have changed in this diff. Show more