Compare commits

...
Author SHA1 Message Date
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
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
moloch-- 87b1efaa5f Make sure zig builds get stripped 2026-02-07 12:25:04 -08:00
moloch-- 009e505a54 Update shellcode unit tests 2026-02-07 10:46:01 -08:00
moloch-- c0dd2d5b51 Update docs, wire linux shellcode into client/ code 2026-02-07 10:33:13 -08:00
moloch-- 6dab9dc6c5 Update malasada to v0.0.2 2026-02-07 10:11:15 -08:00
moloch-- 854249fa25 Add linux shellcode for amd64/arm64 to rpc 2026-02-07 09:33:42 -08:00
moloch-- 7db573a03e Tweak file header 2026-02-07 07:32:48 -08:00
moloch-- 0bef5491dc Fix linux --format shared builds 2026-02-07 00:26:33 -08:00
moloch-- 1c50e5a87b Remove unused tests 2026-02-06 17:53:50 -08:00
moloch-- 8dae2d0d52 v1.7.0 2026-02-06 17:50:35 -08:00
Joe 023776f7e3 Merge pull request #2165 from BishopFox/crack/basic
Update gRPC APIs
2026-02-06 18:48:21 -07:00
moloch-- b7566a3b09 Update client grpc to modern apis 2026-02-06 17:31:08 -08:00
moloch-- b889e2eb6f Fix merge 2026-02-06 16:30:53 -08:00
moloch-- 4466ace279 Merge master 2026-02-06 16:29:39 -08:00
Joe 2b65089b27 Merge pull request #2164 from BishopFox/fix/dns-dos
Fix/dns dos
2026-02-06 15:41:17 -07:00
moloch-- 87d5fd53a8 Add e2e dns c2 tests 2026-02-06 13:54:07 -08:00
moloch-- cf79a2d284 Merge patch for oversized msg size checks 2026-02-06 12:16:03 -08:00
moloch-- 6d0276ff67 Add more tests for dns gc 2026-02-06 11:56:55 -08:00
moloch-- 9b001ab88e Remove totp from dns c2, implement pending msg gc 2026-02-06 11:40:00 -08:00
Joe 503218f3f4 Merge pull request #2163 from BishopFox/feature/exec-bg
Feature/exec bg
2026-02-06 10:44:20 -07:00
moloch-- 495dce0987 Improve error and nil checks in mtls comms 2026-02-06 08:22:26 -08:00
moloch-- a7ff35c229 Tweak selection form height 2026-02-06 07:58:17 -08:00
moloch-- 162fc4fb8a Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:31:10 -08:00
moloch-- 636ea0a9b2 Cleanup execute children output 2026-02-06 07:31:01 -08:00
moloch-- 84ed9bc4e7 Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:27:57 -08:00
moloch-- b985d8ff9f Fix cmd group help 2026-02-06 07:27:50 -08:00
moloch-- 5a43705e1d Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:13:34 -08:00
Joe 76af248850 Merge pull request #2162 from BishopFox/darwin/shellcode
Darwin/arm64 Shellcode
2026-02-06 07:59:56 -07:00
moloch-- 8ea7eb4236 Remove old test 2026-02-05 21:30:45 -08:00
Joe 8f3c3ec1e0 Merge branch 'master' into darwin/shellcode 2026-02-05 22:19:10 -07:00
moloch-- 8d4bf9c759 Fixes to xor_dynamic arm64 2026-02-05 21:18:17 -08:00
moloch-- 628757250e Add execute background flag 2026-02-05 20:56:07 -08:00
moloch-- 85094fc695 Port xor encoders to arm64 2026-02-05 20:41:00 -08:00
moloch-- 0a88ad77f7 Port xor encoders to arm64 2026-02-05 19:15:34 -08:00
moloch-- 4d04f08421 Port xor encoders to arm64 2026-02-05 19:14:25 -08:00
moloch-- 4a01971f7d Allow specifying multiple shellcode encoders by name 2026-02-05 18:39:07 -08:00
moloch-- 977c63dc16 Add shellcode support for darwin/arm64 2026-02-05 17:57:08 -08:00
Joe 2d65d7d6a5 Merge pull request #2161 from BishopFox/c2/mux
C2/mux
2026-02-05 18:36:17 -07:00
moloch-- 4f5ca51cfe Implement generate.darwinShellcode() 2026-02-05 17:16:14 -08:00
moloch-- 83208eeab4 Fix io import for named pipe build 2026-02-05 16:43:53 -08:00
moloch-- 21674a9f0a Merge branch 'codex/fix-client-wg-lport-flag' into c2/mux 2026-02-05 15:50:58 -08:00
moloch-- a18bda14e2 Fix wg lport check 2026-02-05 15:50:47 -08:00
moloch-- 37f9670783 Add mux to wg c2 2026-02-05 15:50:13 -08:00
moloch-- 1745886622 Add mux and signing to wg c2 2026-02-05 14:54:32 -08:00
moloch-- bd7361825c v1.6.11 2026-02-05 12:59:32 -08:00
Joe 32af81c132 Merge pull request #2160 from BishopFox/fix/wepath
Use correct path construction in website.ToProtobuf()
2026-02-05 13:47:35 -07:00
moloch-- 818127349c Use correct path construction in website.ToProtobuf() 2026-02-05 12:29:59 -08:00
moloch-- a712bead88 Tweak mux ids 2026-02-05 12:10:24 -08:00
moloch-- aa86a1d12c Add more signing unit tests 2026-02-05 11:38:37 -08:00
moloch-- afb58f7155 Add envelope signing over mtls 2026-02-05 11:10:35 -08:00
moloch-- 97d5adccd7 Add beacon tests 2026-02-05 09:28:55 -08:00
moloch-- 83cc755388 Add multiplexing to mtls comms 2026-02-05 07:17:25 -08:00
moloch-- 4dc1fe145c Fix missing import 2026-02-04 09:46:38 -08:00
Joe 5e27b23bde Merge pull request #2158 from BishopFox/fix/shell
Fix `shell` pty resizing
2026-02-03 17:47:44 -07:00
Joe 6011d23a9f Merge pull request #2159 from JakubStanior/fix/2129
Use lport from server config in daemon mode  / fix #2129
2026-02-03 17:42:18 -07:00
Jakub Stankiewicz 976d720043 Add default lport from server config in daemon mode 2026-02-03 23:31:08 +01:00
moloch-- 86f41e9abc Fix shell pty resizing 2026-02-02 19:19:14 -08:00
Joe 8b9fc399fa Merge pull request #2156 from BishopFox/fix/armory-win
Ensure armory temp files cross‑plat
2026-02-02 20:12:49 -07:00
moloch-- 27c75c14e6 Ensure armory temp files cross‑plat 2026-02-02 18:17:28 -08:00
moloch-- 11d6af8aa0 Fix duplicated 'unknown cmd' output 2026-02-01 10:44:38 -08:00
moloch-- 54dc6dcec3 v1.6.10 2026-01-31 17:45:38 -08:00
Joe c7412d4d5b Merge pull request #2154 from BishopFox/go/v1.25.6
Go/v1.25.6
2026-01-31 09:44:33 -07:00
moloch-- c3817dc175 Pin tag patch version Go 1.25.6 2026-01-31 08:20:09 -08:00
moloch-- 09cedf588e Update to go 1.25.6 2026-01-31 08:18:39 -08:00
moloch-- 66aed89ebd Merge master 2026-01-31 08:03:24 -08:00
moloch-- 16397e7a0f Update default runtime arg 2026-01-31 07:53:18 -08:00
moloch-- 5062b483b7 Update wasm-donut 2026-01-31 07:43:21 -08:00
moloch-- b268295c8d Switch to wasm-donut 2026-01-30 22:23:56 -08:00
moloch-- 2b3f23cec9 Move donut options to generate flags 2026-01-30 17:19:57 -08:00
moloch-- 25d242230b Rengerate pb 2026-01-30 15:10:02 -08:00
Joe 110658ec39 Merge pull request #2152 from BishopFox/feature/shellcode-encoders
Feature/shellcode encoders
2026-01-30 16:09:46 -07:00
moloch-- b4345226c1 Merge branch 'feature/shellcode-encoders' into crack/basic 2026-01-30 15:09:21 -08:00
moloch-- e99999ebb6 Cleanup shellcode encoder list output 2026-01-30 14:20:48 -08:00
moloch-- 24795e7cba Update shellcode encode rpc to support new encoders 2026-01-30 14:14:45 -08:00
moloch-- 32e3386a3e Add sgn to unified ShellcodeEncoders 2026-01-30 13:40:16 -08:00
moloch-- 7feea8bef3 Add more shellcode encoders 2026-01-30 13:02:14 -08:00
moloch-- d182434d54 Implement amd64/xor_dynamic shellcode encoder 2026-01-30 12:45:22 -08:00
moloch-- 0237af0c6e Implement amd64/xor_dynamic shellcode encoder 2026-01-30 12:45:08 -08:00
moloch-- 1fa36e46c4 Implement amd64/xor_dynamic shellcode encoder 2026-01-30 12:44:49 -08:00
moloch-- 4d9bae0778 Port basic xor shellcode encoder to golang 2026-01-30 12:28:15 -08:00
moloch-- 9a9b40e400 Move sng to server/encoders/shellcode/sgn 2026-01-30 11:51:57 -08:00
moloch-- d784b305ef Move sng to server/encoders/shellcode/sgn 2026-01-30 11:50:24 -08:00
moloch-- 7a224b4757 Update year 2026-01-30 11:40:58 -08:00
Joe 76ae704ce4 Merge pull request #2151 from BishopFox/feature/xxd
Feature/xxd
2026-01-30 12:06:11 -07:00
moloch-- 71cd8fa610 Skip generate tests if there's no edits to related code 2026-01-30 10:35:16 -08:00
moloch-- e9fdcc1285 Improve hex-edit ux 2026-01-30 10:30:15 -08:00
moloch-- 870c995ae2 Add hexedit feature 2026-01-30 10:20:56 -08:00
Joe 15d7e7c4d1 Merge pull request #2150 from BishopFox/feature/chtimes
Expand time formats supported by chtimes
2026-01-30 10:50:56 -07:00
moloch-- 750cdfce0a Expand time formats supported by chtimes 2026-01-30 09:41:55 -08:00
Joe 0f128a22b3 Merge pull request #1807 from navsec/socks5-stability
SOCKS5 Stability Improvements for HTTP/mTLS Transports
2026-01-30 10:07:13 -07:00
Joe 3296a8f483 Merge branch 'master' into socks5-stability
Signed-off-by: Joe <[email protected]>
2026-01-30 10:07:05 -07:00
Joe b77ade2bb9 Merge pull request #2148 from BishopFox/fix/info
Fix/info
2026-01-30 08:47:32 -07:00
moloch-- 41dc54d736 Implement mount for darwin 2026-01-29 22:22:59 -08:00
moloch-- 8a0694cb38 Add mount on darwin 2026-01-29 22:08:28 -08:00
moloch-- 52c0618bc3 Add os version implementations for linux/darwin 2026-01-29 21:50:16 -08:00
moloch-- e2e1912628 Add search to edit cmd 2026-01-29 21:01:16 -08:00
moloch-- 36794f1b07 Implement skip to line number 2026-01-29 20:50:11 -08:00
moloch-- ccd8f75b03 v1.6.9 2026-01-29 20:36:45 -08:00
moloch-- dc89afe404 Add optional line numbers 2026-01-29 20:32:15 -08:00
moloch-- 9db1f3a967 Add syntax highlighting 2026-01-29 20:22:23 -08:00
moloch-- a2ab3aede6 Implement 'edit' command 2026-01-29 20:07:51 -08:00
moloch-- c9f4970e4b Add crack db models 2026-01-29 19:35:39 -08:00
moloch-- e72fa3dbd0 Switch crack.yaml to snake case 2026-01-29 16:30:51 -08:00
moloch-- 2e93ce5684 Merge branch 'master' into crack/basic 2026-01-29 16:16:43 -08:00
Joe 545fc27d03 Merge pull request #2147 from BishopFox/feature/certs-db
Feature/certs db
2026-01-29 09:59:10 -07:00
moloch-- 5bf65aa2b0 Fix notification indentation 2026-01-29 08:51:01 -08:00
Joe ebaf07ff1f Merge branch 'master' into feature/certs-db 2026-01-29 09:16:35 -07:00
moloch-- fc95d3e354 Fix notifications build on windows 2026-01-29 08:15:32 -08:00
moloch-- 88dca9337b Fix notifications docs anchors 2026-01-29 08:11:17 -08:00
moloch-- 44d9f8d5aa Update cli to work with db CAs 2026-01-29 08:01:35 -08:00
moloch-- f76e9e367d Move CAs to db 2026-01-29 07:56:20 -08:00
moloch-- 69fa4143f6 Improve logging for invalid configs 2026-01-29 07:08:34 -08:00
moloch-- dcbda0c64e Improve logging for notifications 2026-01-29 06:50:18 -08:00
moloch-- 3db48408af Initial notifications implementation 2026-01-29 06:44:11 -08:00
moloch-- 828838cddf Re-add new gprc server config fields 2026-01-29 05:52:41 -08:00
moloch-- 16c1dc21fd Merge master 2026-01-29 05:47:18 -08:00
moloch-- 3c8edb7294 v1.6.8 2026-01-28 20:14:36 -08:00
moloch-- 2e2206e0ae Fix emtpy prompt after event 2026-01-28 19:58:08 -08:00
Joe a43648a24a Merge pull request #2146 from BishopFox/fix/websites-css
Fix/websites css
2026-01-28 19:50:20 -07:00
moloch-- f65f98bf65 Improve websites content-type sniffing issue #2033 2026-01-28 18:14:27 -08:00
Joe 26d14e15ad Merge pull request #2144 from BishopFox/fix/update
Fix/update
2026-01-28 19:07:58 -07:00
moloch-- cee2cc76c4 Convert tui-settings to yaml 2026-01-28 18:06:41 -08:00
moloch-- daa33b6301 Convert server configs to yaml syntax, with backwards compatibility 2026-01-28 17:59:34 -08:00
moloch-- ef52973536 Convert configs to yml 2026-01-28 17:39:14 -08:00
moloch-- 6a7bd0803a Update --save help 2026-01-28 17:21:35 -08:00
moloch-- 6c70c1c1e6 Add signature validation logic 2026-01-28 17:20:24 -08:00
moloch-- ddc222b8a2 Improvements to update 2026-01-28 17:07:47 -08:00
moloch-- 5506a582ac Add SliverPublicKey 2026-01-28 16:45:32 -08:00
Joe 63d872c98b Merge pull request #2142 from BishopFox/fix/sgn
Fix/sgn
2026-01-28 17:31:46 -07:00
moloch-- ef9f5dde0d Fix unit tests 2026-01-28 07:21:38 -08:00
moloch-- e35b7fdb97 Update sgn to v0.0.5 2026-01-27 21:28:21 -08:00
moloch-- efbbf382eb Merge branch 'master' into fix/sgn 2026-01-27 21:26:42 -08:00
moloch-- 0b8d00900d v1.6.7 2026-01-27 21:06:39 -08:00
moloch-- 9077596f25 Damn it Claude 2026-01-27 21:04:30 -08:00
moloch-- 0a8104a32e Damn it Claude 2026-01-27 21:03:25 -08:00
moloch-- 15a0ba4c1e Damn it Claude 2026-01-27 21:01:55 -08:00
moloch-- d452c8a676 Write .bin even if sgn fails 2026-01-27 20:49:28 -08:00
Joe fb74b491fa Merge pull request #2141 from BishopFox/fix/console-inputs
Update readline/console deps
2026-01-27 21:38:20 -07:00
moloch-- 2af6e11adc Update readline/console deps 2026-01-27 19:55:18 -08:00
moloch-- 33c656d082 Implement first part of crack cmd 2026-01-24 18:28:07 -08:00
moloch-- 99a44e340f Improve crack benchmark output 2026-01-24 11:50:20 -08:00
moloch-- de5fa3458b Allow keep alives 2026-01-23 17:05:53 -08:00
Joe a3a98858fd Merge pull request #2135 from BishopFox/fix/sqlite
Add sqlite pragmas to db config
2026-01-21 22:28:56 -07:00
moloch-- d0652f2fd3 Add sqlite pragmas to db config 2026-01-21 16:08:07 -08:00
Joe 936f067363 Merge pull request #2130 from BishopFox/feature/website-info
Feature/website info
2026-01-21 10:25:56 -07:00
moloch-- 041c3c7048 Improve tab completer for websites cmd 2026-01-20 21:29:39 -08:00
moloch-- b48f74af58 Add original file name and sha256 to websites cmd 2026-01-20 19:13:42 -08:00
moloch-- 6ec8849b20 v1.6.6 2026-01-16 09:18:39 -08:00
Joe db7cf1ed62 Merge pull request #2125 from BishopFox/fix/winlsdir
Improved `err` and `nil` checks in `dirListHandler`
2026-01-16 10:17:01 -07:00
moloch-- 16e1e77068 Implement better err and nil checks in dirListHandler issue #2122 2026-01-16 07:06:43 -08:00
moloch-- 1cfa694aa2 Merge branch 'update/deps' into tui/adv-forms 2026-01-16 06:57:06 -08:00
moloch-- 5898877136 Fix --rc scripts 2026-01-16 06:56:41 -08:00
moloch-- a7d8cac565 Update generate form 2026-01-16 06:41:58 -08:00
moloch-- 0e73afca47 Update deps 2026-01-16 06:07:10 -08:00
moloch-- 432941d694 Tab complete parent commands from submenu 2026-01-16 05:58:35 -08:00
moloch-- ac521bf53c v1.6.4 2026-01-15 16:10:06 -08:00
Joe 9720c9f14f Merge pull request #2121 from BishopFox/feature/flag-completers
Improved flag completers
2026-01-15 17:09:30 -07:00
moloch-- 4a224fdb9c Improve completers for --interact flags 2026-01-15 15:14:20 -08:00
moloch-- 671eb4d777 Add tab completer to 'use' cmd 2026-01-15 15:03:21 -08:00
moloch-- 16cf816610 Implement os and arch completers 2026-01-15 13:54:21 -08:00
moloch-- 879f576c6e Add MCP to docs 2026-01-15 08:05:02 -08:00
moloch-- 2df78cdbf5 Update readme 2026-01-15 07:45:49 -08:00
moloch-- a05ffc0ae1 Fix footer/card z-index issue in docs site 2026-01-15 07:44:30 -08:00
moloch-- 6b4b218f09 Tweak docs 2026-01-15 07:33:40 -08:00
Joe 4d0d46afd6 Merge pull request #2118 from BishopFox/exec/env
Updates to `execute`
2026-01-13 22:45:02 -07:00
moloch-- f24c057503 Implement --env and --env-inheritance for 'execute' to allow greater control over subprocess env 2026-01-13 20:55:20 -08:00
moloch-- 7e3c1f1549 v1.6.3 2026-01-13 16:28:27 -08:00
Joe 1d4c0ec8a5 Merge pull request #2106 from cmprmsd/feature/extension-alias-autocompletion
Add argument completion for aliases and extensions
2026-01-13 17:25:11 -07:00
Joe 92dda6aa4f Merge pull request #2112 from BishopFox/6b37dependabot/go_modules/google.golang.org/protobuf-1.36.11
Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
2026-01-13 17:24:30 -07:00
Joe 9a605dfda6 Merge pull request #2113 from BishopFox/0f1edependabot/go_modules/golang.org/x/sys-0.40.0
Bump golang.org/x/sys from 0.39.0 to 0.40.0
2026-01-13 17:24:14 -07:00
Joe e918885556 Merge pull request #2114 from Gubarz/getsystem
Fix: getsystem shellcode generation
2026-01-13 17:24:00 -07:00
Joe 263702594f Merge pull request #2115 from Gubarz/execute-assembly
Revert go-donut from 2022 patch to 2021
2026-01-13 17:23:17 -07:00
Joe 72b8dfb454 Merge pull request #2116 from BishopFox/fix/armory-all
Fix armory install all cmd
2026-01-13 17:23:00 -07:00
moloch-- aa2383ac24 Fix armory install all cmd 2026-01-12 21:45:21 -08:00
dependabot[bot] b6f0ea013d Bump golang.org/x/sys from 0.39.0 to 0.40.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/sys/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-13 05:34:38 +00:00
dependabot[bot] eb82e18a52 Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-13 05:34:34 +00:00
Joe 40ea51b685 Merge pull request #2111 from BishopFox/3a38dependabot/go_modules/golang.org/x/net-0.48.0
Bump golang.org/x/net from 0.47.0 to 0.48.0
2026-01-12 22:33:30 -07:00
Joe 1285b44c50 Merge pull request #2110 from BishopFox/d73edependabot/go_modules/github.com/miekg/dns-1.1.70
Bump github.com/miekg/dns from 1.1.69 to 1.1.70
2026-01-12 22:33:18 -07:00
Joe cb63a3378f Merge pull request #2109 from BishopFox/dca1dependabot/go_modules/tailscale.com-1.92.5
Bump tailscale.com from 1.90.6 to 1.92.5
2026-01-12 22:33:02 -07:00
Gubarz 4f90bfdcda revert go-donut from 2022 to 2021 2026-01-12 13:48:53 -07:00
Gubarz 5bb1dcc137 Fix getsystem rpc errors 2026-01-12 12:25:00 -07:00
dependabot[bot] 57c417c48f Bump golang.org/x/net from 0.47.0 to 0.48.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.48.0.
- [Commits](https://github.com/golang/net/compare/v0.47.0...v0.48.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:52 +00:00
dependabot[bot] 55fc59f6f5 Bump github.com/miekg/dns from 1.1.69 to 1.1.70
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.69 to 1.1.70.
- [Commits](https://github.com/miekg/dns/compare/v1.1.69...v1.1.70)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.70
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:44 +00:00
dependabot[bot] 332852d095 Bump tailscale.com from 1.90.6 to 1.92.5
Bumps [tailscale.com](https://github.com/tailscale/tailscale) from 1.90.6 to 1.92.5.
- [Release notes](https://github.com/tailscale/tailscale/releases)
- [Commits](https://github.com/tailscale/tailscale/compare/v1.90.6...v1.92.5)

---
updated-dependencies:
- dependency-name: tailscale.com
  dependency-version: 1.92.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:34 +00:00
cmprmsd a903d2f596 Add support for predefined argument completion choices. in extensions and aliases
Example:
{
      "name": "browser",
      "desc": "target browser (chrome, edge, brave, all)",
      "type": "string",
      "optional": true,
      "choices": ["chrome", "edge", "brave", "all"]
    },
2026-01-11 22:11:40 +01:00
cmprmsd 4a4bb683e3 Add argument completion for aliases and extensions
- Introduced `AliasArgument` struct to define arguments for alias commands.
- Enhanced `LoadAlias` function to build usage and help messages including argument details.
- Implemented argument completion for alias commands using Carapace.
- Updated `makeAliasArgCompleter` to support positional and flag-style argument completions.
- Refactored `makeExtensionArgCompleter` to include similar completion features for extension commands.
- Improved user experience by displaying argument descriptions in completion lists one argument per line (instead of grouped).
2026-01-09 15:46:32 +01:00
moloch-- 016299c756 v1.6.2 2026-01-08 12:56:41 -08:00
Joe 075caaccda Merge pull request #2104 from BishopFox/rc/flush
Flush output on exit
2026-01-08 13:54:54 -07:00
Joe 3c46cf2210 Merge branch 'master' into socks5-stability 2025-10-24 11:41:27 -07:00
Ronan Kervella edcdfb5937 Merge branch 'master' into socks5-stability 2024-12-04 09:36:59 -10:00
Ronan Kervella f81efdb38c Merge branch 'master' into socks5-stability 2024-12-02 15:40:40 -08:00
Ronan Kervella db34ded0a1 Merge branch 'master' into socks5-stability 2024-11-18 15:06:32 -08:00
navsec 8a7a92a9b9 Refine implant/server signaling for tunnel closures 2024-11-18 17:02:15 -06:00
navsec 3eda32f9d7 Performance Optimizations and Implant SOCKS Changes 2024-11-17 15:01:04 -06:00
navsec c18c193ea7 Improve SOCKS lifecycle mangement and resolve leak 2024-11-06 23:24:44 -06:00
navsec 79a8758448 SOCKS5 Stability Improvements for HTTP/mTLS 2024-10-31 12:46:40 -05:00
4902 changed files with 1279899 additions and 19384 deletions

No files matched your search

+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
go-version: "1.25.6"
- name: Install Minisign
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
go-version: "1.25.6"
- name: Checkout repository
uses: actions/checkout@v6
+31 -5
View File
@@ -26,7 +26,7 @@ jobs:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
go-version: "1.25.6"
- name: OS Packages
run: |
@@ -48,8 +48,21 @@ jobs:
- name: Make Linux (amd64)
run: make linux-amd64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test Linux
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
macos-build-test:
@@ -60,7 +73,7 @@ jobs:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
go-version: "1.25.6"
- name: Check Out Code
uses: actions/checkout@v6
@@ -74,8 +87,21 @@ jobs:
- name: Make MacOS (arm64)
run: make macos-arm64
- name: Determine go-tests flags
id: go-tests-flags
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.sha }}"
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
echo "args=" >> "$GITHUB_OUTPUT"
else
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
fi
- name: Test MacOS
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
clients-build:
name: Clients Build
@@ -85,7 +111,7 @@ jobs:
- name: Go 1.25
uses: actions/setup-go@v6
with:
go-version: "^1.25"
go-version: "1.25.6"
- name: Check Out Code
uses: actions/checkout@v6
+3
View File
@@ -29,11 +29,14 @@ MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 24
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
LDFLAGS = -ldflags "-s -w \
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
+1 -4
View File
@@ -39,10 +39,7 @@ Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](h
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### Feedback
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
+11 -6
View File
@@ -34,12 +34,19 @@ const (
SliverClientDirName = ".sliver-client"
versionFileName = "version"
envVarName = "SLIVER_CLIENT_ROOT_DIR"
)
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
func GetRootAppDir() string {
user, _ := user.Current()
dir := filepath.Join(user.HomeDir, SliverClientDirName)
value := os.Getenv(envVarName)
var dir string
if len(value) == 0 {
user, _ := user.Current()
dir = filepath.Join(user.HomeDir, SliverClientDirName)
} else {
dir = value
}
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -108,7 +115,7 @@ func Setup(force bool, echo bool) {
if force || localVer == "" || localVer != ver.GitCommit {
if echo {
fmt.Printf(`
Sliver Copyright (C) 2025 Bishop Fox
Sliver Copyright (C) 2026 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
@@ -116,7 +123,5 @@ under certain conditions; type 'licenses' for details.`)
}
saveAssetVersion(appDir)
}
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
SaveSettings(nil)
}
_, _ = LoadSettings()
}
+27
View File
@@ -0,0 +1,27 @@
package assets
import (
"os"
"path/filepath"
"strings"
)
func legacyBackupPath(legacyPath string) string {
dir := filepath.Dir(legacyPath)
base := filepath.Base(legacyPath)
name := strings.TrimSuffix(base, filepath.Ext(base))
return filepath.Join(dir, "."+name+".json-old")
}
func renameLegacyConfig(legacyPath string) error {
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
return nil
}
backupPath := legacyBackupPath(legacyPath)
if _, err := os.Stat(backupPath); err == nil {
if err := os.Remove(backupPath); err != nil {
return err
}
}
return os.Rename(legacyPath, backupPath)
}
+92 -16
View File
@@ -22,39 +22,111 @@ import (
"encoding/json"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
const (
settingsFileName = "tui-settings.json"
settingsFileName = "tui-settings.yaml"
settingsLegacyFileName = "tui-settings.json"
)
const (
// Accepted values for tui-settings.yaml "prompt:":
// - host: show "[host]" prefix on sliver-client, "[server]" on server console
// - operator-host: show "[operator@host]" prefix on sliver-client, "[server]" on server console
// - basic: show "sliver >" (no prefix)
// - custom: render full prompt from prompt_template
PromptStyleHost = "host"
PromptStyleOperatorHost = "operator-host"
PromptStyleBasic = "basic"
PromptStyleCustom = "custom"
)
// ClientSettings - Client JSON config
type ClientSettings struct {
TableStyle string `json:"tables"`
AutoAdult bool `json:"autoadult"`
BeaconAutoResults bool `json:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow"`
VimMode bool `json:"vim_mode"`
UserConnect bool `json:"user_connect"`
ConsoleLogs bool `json:"console_logs"`
TableStyle string `json:"tables" yaml:"tables"`
AutoAdult bool `json:"autoadult" yaml:"autoadult"`
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"`
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
UserConnect bool `json:"user_connect" yaml:"user_connect"`
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
PromptStyle string `json:"prompt" yaml:"prompt"`
PromptTemplate string `json:"prompt_template" yaml:"prompt_template"`
}
// LoadSettings - Load the client settings from disk
func LoadSettings() (*ClientSettings, error) {
rootDir, _ := filepath.Abs(GetRootAppDir())
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
if err != nil {
settingsPath := filepath.Join(rootDir, settingsFileName)
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
settings := defaultSettings()
migratedLegacy := false
data, err := os.ReadFile(settingsPath)
if err == nil {
if err = yaml.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
} else if !os.IsNotExist(err) {
return defaultSettings(), err
} else if data, err = os.ReadFile(legacyPath); err == nil {
if err = json.Unmarshal(data, settings); err != nil {
return defaultSettings(), err
}
migratedLegacy = true
} else if !os.IsNotExist(err) {
return defaultSettings(), err
}
settings := defaultSettings()
err = json.Unmarshal(data, settings)
if err != nil {
return defaultSettings(), err
// Ensure any missing/unknown values are coerced to a supported prompt style.
settings.PromptStyle = NormalizePromptStyle(settings.PromptStyle)
if err := SaveSettings(settings); err != nil {
return settings, err
}
if migratedLegacy {
if err := renameLegacyConfig(legacyPath); err != nil {
return settings, err
}
}
return settings, nil
}
// NormalizePromptStyle canonicalizes prompt style strings and returns a safe
// default if the value is empty/unknown.
func NormalizePromptStyle(v string) string {
switch strings.ToLower(strings.TrimSpace(v)) {
case PromptStyleOperatorHost:
return PromptStyleOperatorHost
case PromptStyleHost:
return PromptStyleHost
case PromptStyleBasic:
return PromptStyleBasic
case PromptStyleCustom:
return PromptStyleCustom
// Backward compatible aliases.
case "show host":
return PromptStyleHost
case "show user and host":
return PromptStyleOperatorHost
case "show operator and host":
return PromptStyleOperatorHost
case "operator@host":
return PromptStyleOperatorHost
case "user@host":
return PromptStyleOperatorHost
case "":
return PromptStyleHost
default:
return PromptStyleHost
}
}
func defaultSettings() *ClientSettings {
return &ClientSettings{
TableStyle: "SliverDefault",
@@ -64,16 +136,20 @@ func defaultSettings() *ClientSettings {
AlwaysOverflow: false,
VimMode: false,
ConsoleLogs: true,
PromptStyle: PromptStyleHost,
PromptTemplate: DefaultPromptTemplate,
}
}
const DefaultPromptTemplate = `{{- if .IsServer -}}{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- else -}}{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} > {{- end -}}`
// SaveSettings - Save the current settings to disk
func SaveSettings(settings *ClientSettings) error {
rootDir, _ := filepath.Abs(GetRootAppDir())
if settings == nil {
settings = defaultSettings()
}
data, err := json.MarshalIndent(settings, "", " ")
data, err := yaml.Marshal(settings)
if err != nil {
return err
}
+137
View File
@@ -0,0 +1,137 @@
package assets
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestLoadSettingsParsesYAML(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
settingsPath := filepath.Join(rootDir, settingsFileName)
data := []byte(`tables: "Compact"
autoadult: true
beacon_autoresults: false
small_term_width: 120
always_overflow: true
vim_mode: true
user_connect: true
console_logs: false
prompt: "basic"
prompt_template: "{{.Host}}"
`)
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
t.Fatalf("failed to write yaml settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Compact" {
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
}
if !settings.AutoAdult {
t.Fatalf("expected AutoAdult true")
}
if settings.BeaconAutoResults {
t.Fatalf("expected BeaconAutoResults false")
}
if settings.SmallTermWidth != 120 {
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
}
if !settings.AlwaysOverflow {
t.Fatalf("expected AlwaysOverflow true")
}
if !settings.VimMode {
t.Fatalf("expected VimMode true")
}
if !settings.UserConnect {
t.Fatalf("expected UserConnect true")
}
if settings.ConsoleLogs {
t.Fatalf("expected ConsoleLogs false")
}
if settings.PromptStyle != PromptStyleBasic {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleBasic, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Host}}" {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Host}}", settings.PromptTemplate)
}
}
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
rootDir, _ := filepath.Abs(GetRootAppDir())
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
legacy := &ClientSettings{
TableStyle: "Legacy",
AutoAdult: true,
BeaconAutoResults: false,
SmallTermWidth: 200,
AlwaysOverflow: true,
VimMode: true,
UserConnect: true,
ConsoleLogs: false,
PromptStyle: PromptStyleOperatorHost,
PromptTemplate: "{{.Operator}}@{{.Host}} sliver > ",
}
data, err := json.MarshalIndent(legacy, "", " ")
if err != nil {
t.Fatalf("failed to marshal legacy settings: %v", err)
}
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
t.Fatalf("failed to write legacy settings: %v", err)
}
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "Legacy" {
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleOperatorHost {
t.Fatalf("expected PromptStyle %q, got %q", PromptStyleOperatorHost, settings.PromptStyle)
}
if settings.PromptTemplate != "{{.Operator}}@{{.Host}} sliver > " {
t.Fatalf("expected PromptTemplate %q, got %q", "{{.Operator}}@{{.Host}} sliver > ", settings.PromptTemplate)
}
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected yaml settings to exist: %v", err)
}
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
t.Fatalf("expected legacy settings to be renamed: %v", err)
}
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
t.Fatalf("expected legacy backup to exist: %v", err)
}
}
func TestLoadSettingsWritesDefault(t *testing.T) {
t.Setenv(envVarName, t.TempDir())
settings, err := LoadSettings()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if settings.TableStyle != "SliverDefault" {
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
}
if settings.PromptStyle != PromptStyleHost {
t.Fatalf("expected default PromptStyle %q, got %q", PromptStyleHost, settings.PromptStyle)
}
if settings.PromptTemplate != DefaultPromptTemplate {
t.Fatalf("expected default PromptTemplate %q, got %q", DefaultPromptTemplate, settings.PromptTemplate)
}
rootDir, _ := filepath.Abs(GetRootAppDir())
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
t.Fatalf("expected default settings to be written: %v", err)
}
}
+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)
}
}
}
+4 -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,8 +43,6 @@ 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 {
configs := assets.GetConfigs()
@@ -55,7 +50,7 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
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 +67,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)
}
}
}
+1
View File
@@ -25,6 +25,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `creds/` – Implements the 'creds' command group for the Sliver client console.
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
- `edit/` – Implements the 'edit' command for the Sliver client console.
- `environment/` – Implements the 'environment' command group for the Sliver client console.
- `exec/` – Implements the 'exec' command group for the Sliver client console.
- `exit/` – Implements the 'exit' command group for the Sliver client console.
+172 -9
View File
@@ -36,6 +36,7 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
app "github.com/reeflective/console"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -76,6 +77,16 @@ type AliasFile struct {
Path string `json:"path"`
}
// AliasArgument - An argument for an alias command.
type AliasArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
// AliasManifest - The manifest for an alias, contains metadata.
type AliasManifest struct {
Name string `json:"name"`
@@ -89,6 +100,7 @@ type AliasManifest struct {
Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"`
Arguments []*AliasArgument `json:"arguments"`
Files []*AliasFile `json:"files"`
IsReflective bool `json:"is_reflective"`
IsAssembly bool `json:"is_assembly"`
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
}
aliasManifest.RootPath = filepath.Dir(manifestPath)
// Build usage string including arguments
usage := strings.Builder{}
usage.WriteString(aliasManifest.CommandName)
for _, arg := range aliasManifest.Arguments {
usage.WriteString(" ")
if arg.Optional {
usage.WriteString("[")
}
usage.WriteString(strings.ToUpper(arg.Name))
if arg.Optional {
usage.WriteString("]")
}
}
// Build long help message
longHelp := strings.Builder{}
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
longHelp.WriteString(usage.String())
longHelp.WriteString("\n")
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
if len(aliasManifest.Help) > 0 {
longHelp.WriteString(aliasManifest.Help)
longHelp.WriteString("\n")
}
if len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString(aliasManifest.LongHelp)
longHelp.WriteString("\n")
}
}
if len(aliasManifest.Arguments) > 0 {
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
for _, arg := range aliasManifest.Arguments {
longHelp.WriteString("\n\t")
optStr := ""
if arg.Optional {
optStr = "[OPTIONAL]"
}
aType := arg.Type
if aType == "wstring" {
aType = "string"
}
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
addAliasCmd := &cobra.Command{
Use: aliasManifest.CommandName,
Use: usage.String(),
Short: helpMsg,
Long: longHelpMsg,
Long: help.FormatHelpTmpl(longHelp.String()),
Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args)
},
@@ -182,9 +239,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.StringP("app-domain", "d", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.StringP("arch", "a", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
f.BoolP("in-process", "i", false, "Run in the current sliver process")
f.StringP("runtime", "r", "", "Runtime to use for running the assembly (only supported when used with --in-process)")
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows (only supported when used with --in-process)")
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows (only supported when used with --in-process)")
f.StringP("runtime", "r", "v4.0.30319", "Runtime to use for running the assembly")
f.BoolP("amsi-bypass", "M", false, "Bypass AMSI on Windows")
f.BoolP("etw-bypass", "E", false, "Bypass ETW on Windows")
addAliasCmd.Flags().AddFlagSet(f)
}
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
addAliasCmd.Flags().AddFlagSet(f)
// Setup completions for alias arguments
comps := carapace.Gen(addAliasCmd)
makeAliasArgCompleter(aliasManifest, comps)
cmd.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd
@@ -313,7 +374,7 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
}
con.PrintWarnf("%s", msgStr)
confirm := false
_ = forms.Confirm("Do you want to continue?", &confirm)
forms.Confirm("Do you want to continue?", &confirm)
if !confirm {
return
}
@@ -589,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
app.CommandFilterKey: strings.Join(all, ","),
}
}
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --target, --port)
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
if len(alias.Arguments) == 0 {
return
}
var actions []carapace.Action
for _, arg := range alias.Arguments {
var action carapace.Action
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("alias data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
if arg.Optional {
usage += " (optional)"
}
actions = append(actions, action.Usage("%s", usage))
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// Pre-build the flag completions at registration time (not in a callback)
flagCompletion := makeAliasFlagNameCompletion(alias)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range alias.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
case "bool":
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
// makeAliasFlagNameCompletion creates completion for flag names
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
var results []string
for _, arg := range alias.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
}
+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",
+52 -23
View File
@@ -274,19 +274,22 @@ func getInstalledPackageNames() []string {
}
// This is a convenience function to get the names of the commands in the cache
func getCommandsInCache() []string {
func getCommandsInCache(armoryPK string) []string {
commandNames := []string{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
}
if cacheEntry.Pkg.IsAlias {
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
}
} else {
for _, command := range cacheEntry.Extension.ExtCommand {
if !slices.Contains(commandNames, command.CommandName) {
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
commandNames = append(commandNames, command.CommandName)
}
}
@@ -398,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
var requestedPackageList []string
if name == "all" {
requestedPackageList = []string{}
allCommands := getCommandsInCache()
allCommands := getCommandsInCache(armoryPK)
for _, cmdName := range allCommands {
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
// Check to see if there is a package pending with that name
@@ -458,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
if err != nil {
return err
}
if len(packageInstallList) == 0 && name == "all" {
availableCommands := getCommandsInCache(armoryPK)
if len(availableCommands) == 0 {
con.PrintInfof("No packages or bundles found\n")
} else {
con.PrintInfof("All available packages are already installed\n")
}
return nil
}
if len(packageInstallList) > 0 {
for _, packageID := range packageInstallList {
entry := packageCacheLookupByID(packageID)
@@ -623,20 +635,15 @@ func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientCon
return errors.New("signature verification failed")
}
tmpFile, err := os.CreateTemp("", "sliver-armory-")
tmpFileName, err := writeArmoryTempFile(tarGz)
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
defer os.Remove(tmpFileName)
con.Printf(console.Clearln + "\r") // Clear the line
installPath := alias.InstallFromFile(tmpFile.Name(), entry.Alias.CommandName, promptToOverwrite, con)
installPath := alias.InstallFromFile(tmpFileName, entry.Alias.CommandName, promptToOverwrite, con)
if installPath == nil {
return errors.New("failed to install alias")
}
@@ -717,23 +724,45 @@ func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clien
return errors.New("signature verification failed")
}
tmpFile, err := os.CreateTemp("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
err = tmpFile.Sync()
tmpFileName, err := writeArmoryTempFile(tarGz)
if err != nil {
return err
}
defer os.Remove(tmpFileName)
con.Printf(console.Clearln + "\r") // Clear download message
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
extensions.InstallFromDir(tmpFileName, promptToOverwrite, con, true)
return nil
}
func writeArmoryTempFile(data []byte) (string, error) {
if len(data) == 0 {
return "", errors.New("downloaded archive is empty")
}
tmpFile, err := os.CreateTemp("", "sliver-armory-")
if err != nil {
return "", err
}
tmpFileName := tmpFile.Name()
for len(data) > 0 {
n, err := tmpFile.Write(data)
if err != nil {
tmpFile.Close()
os.Remove(tmpFileName)
return "", err
}
data = data[n:]
}
if err := tmpFile.Sync(); err != nil {
tmpFile.Close()
os.Remove(tmpFileName)
return "", err
}
if err := tmpFile.Close(); err != nil {
os.Remove(tmpFileName)
return "", err
}
return tmpFileName, nil
}
+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",
+32 -19
View File
@@ -39,6 +39,7 @@ import (
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
killFlag, _ := cmd.Flags().GetString("kill")
killAll, _ := cmd.Flags().GetBool("kill-all")
interact, _ := cmd.Flags().GetString("interact")
// Handle kill
if killFlag != "" {
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
}
}
if interact != "" {
beacon, err := GetBeacon(con, interact)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.ActiveTarget.Set(nil, beacon)
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
return
}
filter, _ := cmd.Flags().GetString("filter")
var filterRegex *regexp.Regexp
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
@@ -165,10 +178,10 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
}
for _, beacon := range beacons {
color := console.Normal
style := console.StyleNormal
activeBeacon := con.ActiveTarget.GetBeacon()
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
color = console.Green
style = console.StyleGreen
}
if beacon.Integrity == "" {
beacon.Integrity = "-"
@@ -179,34 +192,34 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s (%d)"+console.Normal, beacon.Filename, beacon.PID),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(fmt.Sprintf("%d/%d", beacon.TasksCountCompleted, beacon.TasksCount)),
style.Render(beacon.Transport),
style.Render(beacon.RemoteAddress),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s (%d)", beacon.Filename, beacon.PID)),
}
if windowsBeaconInList {
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, beacon.Integrity))
rowEntries = append(rowEntries, style.Render(beacon.Integrity))
}
rowEntries = append(rowEntries, []string{
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Locale),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
style.Render(beacon.Locale),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}...)
} else {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
style.Render(strings.Split(beacon.ID, "-")[0]),
style.Render(beacon.Name),
style.Render(beacon.Transport),
style.Render(beacon.Hostname),
style.Render(strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
style.Render(fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch)),
con.FormatDateDelta(time.Unix(beacon.LastCheckin, 0), wideTermWidth, false),
con.FormatDateDelta(time.Unix(beacon.NextCheckin, 0), wideTermWidth, true),
}
+70 -15
View File
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
f.StringP("kill", "k", "", "kill the designated beacon")
f.BoolP("kill-all", "K", false, "kill all beacons")
f.StringP("interact", "i", "", "interact with a beacon")
f.BoolP("force", "F", false, "force killing the beacon")
f.StringP("filter", "f", "", "filter beacons by substring")
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
(*comp)["kill"] = BeaconIDCompleter(con)
(*comp)["interact"] = BeaconIDCompleter(con)
})
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
beaconsRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a beacon",
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
// BeaconIDCompleter completes beacon IDs
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err == nil {
for _, b := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, b.ID[:8])
results = append(results, desc)
}
}
return carapace.ActionValuesDescribed(results...).Tag("beacons")
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
}
return carapace.ActionCallback(callback)
}
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values := describedValuesToTabs(beaconCompletionPairs(con))
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func beaconCompletionPairs(con *console.SliverClient) []string {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return results
}
for _, b := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, b.ID[:8], desc)
}
return results
}
func describedValuesToTabs(values []string) []string {
tabbed := make([]string, 0, len(values)/2)
for i := 0; i+1 < len(values); i += 2 {
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
}
return tabbed
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+2
View File
@@ -6,5 +6,7 @@ Implements the 'certificates' command group for the Sliver client console.
## Go Files
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
- `completions.go` – Provides tab completion helpers for certificate common names.
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
@@ -0,0 +1,89 @@
package certificates
import (
"context"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
return
}
printCertificateAuthorityInfo(con, authorityInfo.Info)
}
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
width, _, err := term.GetSize(0)
if err != nil {
width = 999
}
if len(authData) == 0 {
con.PrintWarnf("There are no certificate authorities in the database.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
wideTermWidth := con.Settings.SmallTermWidth < width
if wideTermWidth {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Creation Time",
"Authority Type",
"Key Algorithm",
"Validity Start",
"Expires",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Common Name",
"Expires",
})
}
for _, authority := range authData {
rowStyle := console.StyleNormal
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
if err == nil {
rowStyle = checkCertExpiry(expiry)
}
if wideTermWidth {
tw.AppendRow(table.Row{
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.CreationTime),
rowStyle.Render(authority.Type),
rowStyle.Render(authority.KeyAlgorithm),
rowStyle.Render(authority.ValidityStart),
rowStyle.Render(authority.ValidityExpiry),
})
} else {
tw.AppendRow(table.Row{
rowStyle.Render(authority.ID),
rowStyle.Render(authority.CN),
rowStyle.Render(authority.ValidityExpiry),
})
}
}
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
con.Printf("%s\n", tw.Render())
}
+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),
})
}
+14
View File
@@ -23,6 +23,7 @@ import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -44,6 +45,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("server", "s", false, "Show server certificates")
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
})
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
(*comp)["cn"] = CertificateCommonNameCompleter(con)
})
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
authoritiesCmd := &cobra.Command{
Use: "authorities",
Short: "List certificate authorities",
Run: func(cmd *cobra.Command, args []string) {
CertificateAuthoritiesCmd(cmd, con, args)
},
}
certificatesCmd.AddCommand(authoritiesCmd)
return []*cobra.Command{
certificatesCmd,
@@ -0,0 +1,96 @@
package certificates
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// CertificateCommonNameCompleter completes common names for stored certificates.
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
if con == nil || con.Rpc == nil {
return carapace.ActionMessage("not connected")
}
values, err := certificateCommonNameValues(con)
if err != nil {
return carapace.ActionMessage("failed to list certificates %s", err)
}
if len(values) == 0 {
return carapace.ActionMessage("no certificates")
}
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
})
}
func registerCertificateCNFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values, err := certificateCommonNameValues(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
if con == nil || con.Rpc == nil {
return nil, nil
}
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
CategoryFilters: AllTransports | AllRoles,
})
if err != nil {
return nil, err
}
unique := map[string]struct{}{}
for _, cert := range certInfo.Info {
if cert.CN == "" {
continue
}
unique[cert.CN] = struct{}{}
}
values := make([]string, 0, len(unique))
for name := range unique {
values = append(values, name)
}
sort.Strings(values)
return values, nil
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+4
View File
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
bindCrackFlags(f)
})
crackStationsCmd := &cobra.Command{
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
CrackStationsCmd(cmd, con, args)
},
}
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
})
crackCmd.AddCommand(crackStationsCmd)
wordlistsCmd := &cobra.Command{
+326
View File
@@ -0,0 +1,326 @@
package crack
import (
"fmt"
"strconv"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
if len(args) > 0 {
return true
}
hasFlag := false
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Changed && flag.Name != "timeout" {
hasFlag = true
}
})
return hasFlag
}
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
flags := cmd.Flags()
req := &clientpb.CrackCommand{}
if flags.Changed("attack-mode") {
raw, _ := flags.GetString("attack-mode")
attackMode, err := parseCrackAttackMode(raw)
if err != nil {
return nil, err
}
req.AttackMode = attackMode
}
if flags.Changed("hash-type") {
raw, _ := flags.GetString("hash-type")
hashType, err := parseHashType(raw)
if err != nil {
return nil, err
}
req.HashType = hashType
}
hashes, _ := flags.GetStringSlice("hash")
if len(args) > 0 {
hashes = append(hashes, args...)
}
req.Hashes = hashes
req.Quiet, _ = flags.GetBool("quiet")
req.HexCharset, _ = flags.GetBool("hex-charset")
req.HexSalt, _ = flags.GetBool("hex-salt")
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
req.Force, _ = flags.GetBool("force")
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
req.Status, _ = flags.GetBool("status")
req.StatusJSON, _ = flags.GetBool("status-json")
req.StatusTimer, _ = flags.GetUint32("status-timer")
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
req.MachineReadable, _ = flags.GetBool("machine-readable")
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
req.Loopback, _ = flags.GetBool("loopback")
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
req.MarkovHcstat2 = []byte(raw)
}
req.MarkovDisable, _ = flags.GetBool("markov-disable")
req.MarkovClassic, _ = flags.GetBool("markov-classic")
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
req.Runtime, _ = flags.GetUint32("runtime")
req.Session, _ = flags.GetString("session")
req.Restore, _ = flags.GetBool("restore")
req.RestoreDisable, _ = flags.GetBool("restore-disable")
if raw, _ := flags.GetString("restore-file"); raw != "" {
req.RestoreFile = []byte(raw)
}
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
for _, raw := range formats {
format, err := parseCrackOutfileFormat(raw)
if err != nil {
return nil, err
}
req.OutfileFormat = append(req.OutfileFormat, format)
}
}
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
req.Separator, _ = flags.GetString("separator")
req.Stdout, _ = flags.GetBool("stdout")
req.Show, _ = flags.GetBool("show")
req.Left, _ = flags.GetBool("left")
req.Username, _ = flags.GetBool("username")
req.Remove, _ = flags.GetBool("remove")
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
if raw, _ := flags.GetString("potfile"); raw != "" {
req.Potfile = []byte(raw)
}
if flags.Changed("encoding-from") {
raw, _ := flags.GetString("encoding-from")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingFrom = enc
}
if flags.Changed("encoding-to") {
raw, _ := flags.GetString("encoding-to")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingTo = enc
}
req.DebugMode, _ = flags.GetUint32("debug-mode")
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
req.KeyboardLayoutMapping = []byte(raw)
}
req.Benchmark, _ = flags.GetBool("benchmark")
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
req.SpeedOnly, _ = flags.GetBool("speed-only")
req.ProgressOnly, _ = flags.GetBool("progress-only")
req.SegmentSize, _ = flags.GetUint32("segment-size")
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
req.HookThreads, _ = flags.GetUint32("hook-threads")
req.HashInfo, _ = flags.GetBool("hash-info")
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
req.BackendInfo, _ = flags.GetBool("backend-info")
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
if flags.Changed("workload-profile") {
raw, _ := flags.GetString("workload-profile")
profile, err := parseCrackWorkloadProfile(raw)
if err != nil {
return nil, err
}
req.WorkloadProfile = profile
}
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
req.SpinDamp, _ = flags.GetUint32("spin-damp")
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
req.Skip, _ = flags.GetUint64("skip")
req.Limit, _ = flags.GetUint64("limit")
req.Keyspace, _ = flags.GetBool("keyspace")
if raw, _ := flags.GetString("rules-file"); raw != "" {
req.RulesFile = []byte(raw)
}
req.GenerateRules, _ = flags.GetUint32("generate-rules")
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
req.CustomCharset1, _ = flags.GetString("custom-charset1")
req.CustomCharset2, _ = flags.GetString("custom-charset2")
req.CustomCharset3, _ = flags.GetString("custom-charset3")
req.CustomCharset4, _ = flags.GetString("custom-charset4")
req.Identify, _ = flags.GetString("identify")
req.Increment, _ = flags.GetBool("increment")
req.IncrementMin, _ = flags.GetUint32("increment-min")
req.IncrementMax, _ = flags.GetUint32("increment-max")
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
req.BrainServer, _ = flags.GetBool("brain-server")
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
req.BrainClient, _ = flags.GetBool("brain-client")
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
req.BrainHost, _ = flags.GetString("brain-host")
req.BrainPort, _ = flags.GetUint32("brain-port")
req.BrainPassword, _ = flags.GetString("brain-password")
req.BrainSession, _ = flags.GetString("brain-session")
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
return req, nil
}
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
values, _ := flags.GetUintSlice(name)
if len(values) == 0 {
return nil
}
out := make([]uint32, len(values))
for i, value := range values {
out[i] = uint32(value)
}
return out
}
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
if raw == "" {
return clientpb.CrackAttackMode_STRAIGHT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackAttackMode(numeric), nil
}
normalized := strings.ToLower(strings.TrimSpace(raw))
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
switch normalized {
case "straight":
return clientpb.CrackAttackMode_STRAIGHT, nil
case "combination":
return clientpb.CrackAttackMode_COMBINATION, nil
case "bruteforce", "brute_force", "brute-force":
return clientpb.CrackAttackMode_BRUTEFORCE, nil
case "hybrid_wordlist_mask":
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
case "hybrid_mask_wordlist":
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
case "association":
return clientpb.CrackAttackMode_ASSOCIATION, nil
case "no_attack", "none":
return clientpb.CrackAttackMode_NO_ATTACK, nil
default:
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
}
}
func parseHashType(raw string) (clientpb.HashType, error) {
if raw == "" {
return clientpb.HashType_MD5, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.HashType(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.HashType_value[key]; ok {
return clientpb.HashType(value), nil
}
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
}
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
if raw == "" {
return clientpb.CrackEncoding_INVALID_ENCODING, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackEncoding(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackEncoding_value[key]; ok {
return clientpb.CrackEncoding(value), nil
}
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
}
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
if raw == "" {
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackOutfileFormat(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
return clientpb.CrackOutfileFormat(value), nil
}
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
}
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
if raw == "" {
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackWorkloadProfile(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
return clientpb.CrackWorkloadProfile(value), nil
}
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
}
func normalizeEnumKey(value string) string {
if value == "" {
return ""
}
var builder strings.Builder
builder.Grow(len(value))
underscore := false
for i := 0; i < len(value); i++ {
ch := value[i]
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
if ch >= 'a' && ch <= 'z' {
ch -= 'a' - 'A'
}
builder.WriteByte(ch)
underscore = false
continue
}
if !underscore {
builder.WriteByte('_')
underscore = true
}
}
return strings.Trim(builder.String(), "_")
}
+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"})
+108
View File
@@ -0,0 +1,108 @@
package crack
import "github.com/spf13/pflag"
func bindCrackFlags(f *pflag.FlagSet) {
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
f.String("hash-type", "", "hashcat hash type (number or enum name)")
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
f.Bool("quiet", false, "quiet mode")
f.Bool("hex-charset", false, "treat charset as hex")
f.Bool("hex-salt", false, "treat salt as hex")
f.Bool("hex-wordlist", false, "treat wordlist as hex")
f.Bool("force", false, "ignore warnings")
f.Bool("deprecated-check-disable", false, "disable deprecated check")
f.Bool("status", false, "enable status")
f.Bool("status-json", false, "emit status in JSON")
f.Uint32("status-timer", 0, "status timer interval (seconds)")
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
f.Bool("machine-readable", false, "machine readable output")
f.Bool("keep-guessing", false, "keep guessing")
f.Bool("self-test-disable", false, "disable self-test")
f.Bool("loopback", false, "use loopback mode")
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
f.Bool("markov-disable", false, "disable markov mode")
f.Bool("markov-classic", false, "use classic markov")
f.Bool("markov-inverse", false, "use inverse markov")
f.Uint32("markov-threshold", 0, "markov threshold")
f.Uint32("runtime", 0, "runtime in seconds")
f.String("session", "", "session name")
f.Bool("restore", false, "restore session")
f.Bool("restore-disable", false, "disable restore")
f.String("restore-file", "", "restore file path or identifier")
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
f.String("separator", "", "separator character")
f.Bool("stdout", false, "stdout output")
f.Bool("show", false, "show cracked hashes")
f.Bool("left", false, "show uncracked hashes")
f.Bool("username", false, "enable username handling")
f.Bool("remove", false, "remove cracked hashes")
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
f.Bool("potfile-disable", false, "disable potfile")
f.String("potfile", "", "potfile path or identifier")
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
f.Uint32("debug-mode", 0, "debug mode mask")
f.Bool("logfile-disable", false, "disable logfile")
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
f.Bool("benchmark", false, "run benchmark")
f.Bool("benchmark-all", false, "run full benchmark")
f.Bool("speed-only", false, "show speed only")
f.Bool("progress-only", false, "show progress only")
f.Uint32("segment-size", 0, "segment size")
f.Uint32("bitmap-min", 0, "bitmap min")
f.Uint32("bitmap-max", 0, "bitmap max")
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
f.Uint32("hook-threads", 0, "hook threads")
f.Bool("hash-info", false, "show hash information")
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
f.Bool("backend-info", false, "show backend info")
f.UintSlice("backend-devices", nil, "backend device IDs")
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
f.Uint32("kernel-accel", 0, "kernel acceleration")
f.Uint32("kernel-loops", 0, "kernel loops")
f.Uint32("kernel-threads", 0, "kernel threads")
f.Uint32("backend-vector-width", 0, "backend vector width")
f.Uint32("spin-damp", 0, "spin damp")
f.Bool("hwmon-disable", false, "disable hardware monitoring")
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
f.Uint64("skip", 0, "skip count")
f.Uint64("limit", 0, "limit count")
f.Bool("keyspace", false, "show keyspace")
f.String("rules-file", "", "rules file path or identifier")
f.Uint32("generate-rules", 0, "generate rules")
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
f.String("generate-rules-func-sel", "", "generate rules function selection")
f.Int32("generate-rules-seed", 0, "generate rules seed")
f.String("custom-charset1", "", "custom charset 1")
f.String("custom-charset2", "", "custom charset 2")
f.String("custom-charset3", "", "custom charset 3")
f.String("custom-charset4", "", "custom charset 4")
f.String("identify", "", "identify hash")
f.Bool("increment", false, "incremental mode")
f.Uint32("increment-min", 0, "increment min")
f.Uint32("increment-max", 0, "increment max")
f.Bool("slow-candidates", false, "slow candidates")
f.Bool("brain-server", false, "brain server mode")
f.Uint32("brain-server-timer", 0, "brain server timer")
f.Bool("brain-client", false, "brain client mode")
f.String("brain-client-features", "", "brain client features")
f.String("brain-host", "", "brain host")
f.Uint32("brain-port", 0, "brain port")
f.String("brain-password", "", "brain password")
f.String("brain-session", "", "brain session")
f.String("brain-session-whitelist", "", "brain session whitelist")
}
+68 -25
View File
@@ -22,6 +22,7 @@ import (
"context"
"fmt"
"sort"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -34,6 +35,37 @@ import (
// CrackCmd - GPU password cracking interface
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunCrack(cmd, args) {
crackCmd, err := buildCrackCommand(cmd, args)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
ctx := context.Background()
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
}
resp, err := con.Rpc.Crack(ctx, crackCmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if resp == nil || resp.Job == nil {
con.PrintInfof("Crack request submitted\n")
return
}
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
if resp.Job.Err != "" {
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
}
return
}
if !AreCrackersOnline(con) {
PrintNoCrackstations(con)
} else {
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
con.PrintErrorf("%s\n", err)
return
}
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
if len(crackers.Crackstations) == 0 {
PrintNoCrackstations(con)
} else {
PrintCrackers(crackers.Crackstations, con)
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
}
}
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
return len(crackers.Crackstations) > 0
}
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
sort.Slice(crackers, func(i, j int) bool {
return crackers[i].Name < crackers[j].Name
})
for index, cracker := range crackers {
printCracker(cracker, index, con)
printCracker(cracker, index, con, showBenchmarks)
if index < len(crackers)-1 {
con.Println()
con.Println()
@@ -96,53 +129,63 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
}
}
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
tw.SetTitle(console.StyleBoldOrange.Render(fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName)) + "\n")
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Operating System" + console.Normal, fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.Bold + "Hashcat Version" + console.Normal, cracker.HashcatVersion})
tw.AppendRow(table.Row{console.StyleBold.Render("Operating System"), fmt.Sprintf("%s/%s", cracker.GOOS, cracker.GOARCH)})
tw.AppendRow(table.Row{console.StyleBold.Render("Hashcat Version"), cracker.HashcatVersion})
if 0 < len(cracker.CUDA) {
for _, cuda := range cracker.CUDA {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "CUDA Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, cuda.Name, cuda.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", cuda.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("CUDA Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", cuda.Name, cuda.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", cuda.MemoryFree, cuda.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", cuda.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", cuda.Processors)})
}
}
if 0 < len(cracker.Metal) {
for _, metal := range cracker.Metal {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "Metal Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+"%s (%s)"+console.Normal, metal.Name, metal.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", metal.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("Metal Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", metal.Name, metal.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", metal.MemoryFree, metal.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", metal.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", metal.Processors)})
}
}
if 0 < len(cracker.OpenCL) {
for _, openCL := range cracker.OpenCL {
tw.AppendSeparator()
tw.AppendRow(table.Row{console.Bold + "OpenCL Device" + console.Normal, fmt.Sprintf(console.Bold+console.Green+console.Bold+"%s (%s)"+console.Normal, openCL.Name, openCL.Version)})
tw.AppendRow(table.Row{console.Bold + "Memory" + console.Normal, fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.Bold + "Clock" + console.Normal, fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.Bold + "Processors" + console.Normal, fmt.Sprintf("%d", openCL.Processors)})
tw.AppendRow(table.Row{console.StyleBold.Render("OpenCL Device"), console.StyleBoldGreen.Render(fmt.Sprintf("%s (%s)", openCL.Name, openCL.Version))})
tw.AppendRow(table.Row{console.StyleBold.Render("Memory"), fmt.Sprintf("%s free of %s", openCL.MemoryFree, openCL.MemoryTotal)})
tw.AppendRow(table.Row{console.StyleBold.Render("Clock"), fmt.Sprintf("%d", openCL.Clock)})
tw.AppendRow(table.Row{console.StyleBold.Render("Processors"), fmt.Sprintf("%d", openCL.Processors)})
}
}
con.Printf("%s\n", tw.Render())
con.Println()
printBenchmarks(cracker, con)
if showBenchmarks {
con.Println()
printBenchmarks(cracker, con)
}
}
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
tw.SetTitle(console.StyleBold.Render("Benchmarks"))
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
for hashType, speed := range cracker.Benchmarks {
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
if len(cracker.Benchmarks) == 0 {
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
} else {
for hashType, speed := range cracker.Benchmarks {
name, ok := hashcatHashTypeName(hashType)
if !ok {
name = clientpb.HashType(hashType).String()
}
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
}
}
con.Printf("%s\n", tw.Render())
}
+419
View File
@@ -0,0 +1,419 @@
package crack
import "fmt"
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
var hashcatHashTypes = map[int32]string{
0: "MD5",
10: "md5($pass.$salt)",
11: "Joomla < 2.5.18",
12: "PostgreSQL",
20: "md5($salt.$pass)",
21: "osCommerce, xt:Commerce",
22: "Juniper NetScreen/SSG (ScreenOS)",
23: "Skype",
24: "SolarWinds Serv-U",
30: "md5(utf16le($pass).$salt)",
40: "md5($salt.utf16le($pass))",
50: "HMAC-MD5 (key = $pass)",
60: "HMAC-MD5 (key = $salt)",
70: "md5(utf16le($pass))",
100: "SHA1",
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
110: "sha1($pass.$salt)",
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
112: "Oracle S: Type (Oracle 11+)",
120: "sha1($salt.$pass)",
121: "SMF (Simple Machines Forum) > v1.1",
122: "macOS v10.4, macOS v10.5, macOS v10.6",
123: "EPi",
124: "Django (SHA-1)",
125: "ArubaOS",
130: "sha1(utf16le($pass).$salt)",
131: "MSSQL (2000)",
132: "MSSQL (2005)",
133: "PeopleSoft",
140: "sha1($salt.utf16le($pass))",
141: "Episerver 6.x < .NET 4",
150: "HMAC-SHA1 (key = $pass)",
160: "HMAC-SHA1 (key = $salt)",
170: "sha1(utf16le($pass))",
190: "sha1(LinkedIn)",
200: "MySQL323",
300: "MySQL4.1/MySQL5",
400: "phpass, phpBB3 (MD5)",
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
501: "Juniper IVE",
600: "BLAKE2b-512",
900: "MD4",
1000: "NTLM",
1100: "Domain Cached Credentials (DCC), MS Cache",
1300: "SHA2-224",
1400: "SHA2-256",
1410: "sha256($pass.$salt)",
1411: "SSHA-256(Base64), LDAP {SSHA256}",
1420: "sha256($salt.$pass)",
1421: "hMailServer",
1430: "sha256(utf16le($pass).$salt)",
1431: "base64(sha256(unicode($pass)))",
1440: "sha256($salt.utf16le($pass))",
1441: "Episerver 6.x >= .NET 4",
1450: "HMAC-SHA256 (key = $pass)",
1460: "HMAC-SHA256 (key = $salt)",
1470: "sha256(utf16le($pass))",
1500: "descrypt, DES (Unix), Traditional DES",
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
1700: "SHA2-512",
1710: "sha512($pass.$salt)",
1711: "SSHA-512(Base64), LDAP {SSHA512}",
1720: "sha512($salt.$pass)",
1722: "macOS v10.7",
1730: "sha512(utf16le($pass).$salt)",
1731: "MSSQL (2012, 2014)",
1740: "sha512($salt.utf16le($pass))",
1750: "HMAC-SHA512 (key = $pass)",
1760: "HMAC-SHA512 (key = $salt)",
1770: "sha512(utf16le($pass))",
1800: "sha512crypt $6$, SHA512 (Unix)",
2000: "STDOUT",
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
2400: "Cisco-PIX MD5",
2410: "Cisco-ASA MD5",
2500: "WPA-EAPOL-PBKDF2",
2501: "WPA-EAPOL-PMK",
2600: "md5(md5($pass))",
2611: "vBulletin < v3.8.5",
2612: "PHPS",
2711: "vBulletin >= v3.8.5",
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
3000: "LM",
3100: "Oracle H: Type (Oracle 7+)",
3200: "bcrypt $2\\*$, Blowfish (Unix)",
3300: "MD5(Sun)",
3500: "md5(md5(md5($pass)))",
3610: "md5(md5($salt).$pass )",
3710: "md5($salt.md5($pass))",
3711: "MediaWiki B type",
3720: "md5($pass.md5($salt))",
3721: "WebEdition CMS",
3800: "md5($salt.$pass.$salt)",
3910: "md5(md5($pass).md5($salt))",
4010: "md5($salt.md5($salt.$pass))",
4110: "md5($salt.md5($pass.$salt))",
4210: "md5($username.0.$pass)",
4300: "md5(strtoupper(md5($pass)))",
4400: "md5(sha1($pass))",
4500: "sha1(sha1($pass))",
4510: "sha1(sha1($pass).$salt)",
4520: "sha1($salt.sha1($pass))",
4521: "Redmine",
4522: "PunBB",
4600: "sha1(sha1(sha1($pass)))",
4700: "sha1(md5($pass))",
4710: "sha1(md5($pass).$salt)",
4711: "Huawei sha1(md5($pass).$salt)",
4800: "iSCSI CHAP authentication, MD5(CHAP)",
4900: "sha1($salt.$pass.$salt)",
5000: "sha1(sha1($salt.$pass.$salt))",
5100: "Half MD5",
5200: "Password Safe v3",
5300: "IKE-PSK MD5",
5400: "IKE-PSK SHA1",
5500: "NetNTLMv1 / NetNTLMv1+ESS",
5600: "NetNTLMv2",
5700: "Cisco-IOS type 4 (SHA256)",
5800: "Samsung Android Password/PIN",
6000: "RIPEMD-160",
6100: "Whirlpool",
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
6300: "AIX {smd5}",
6400: "AIX {ssha256}",
6500: "AIX {ssha512}",
6600: "1Password, agilekeychain",
6700: "AIX {ssha1}",
6800: "LastPass + LastPass sniffed",
6900: "GOST R 34.11-94",
7000: "FortiGate (FortiOS)",
7100: "macOS v10.8+ (PBKDF2-SHA512)",
7200: "GRUB 2",
7300: "IPMI2 RAKP HMAC-SHA1",
7400: "sha256crypt $5$, SHA256 (Unix)",
7401: "MySQL $A$ (sha256crypt)",
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
7700: "SAP CODVN B (BCODE)",
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
7800: "SAP CODVN F/G (PASSCODE)",
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
7900: "Drupal7",
8000: "Sybase ASE",
8100: "Citrix NetScaler (SHA1)",
8200: "1Password, cloudkeychain",
8300: "DNSSEC (NSEC3)",
8400: "WBB3 (Woltlab Burning Board)",
8500: "RACF",
8600: "Lotus Notes/Domino 5",
8700: "Lotus Notes/Domino 6",
8800: "Android FDE <= 4.3",
8900: "scrypt",
9000: "Password Safe v2",
9100: "Lotus Notes/Domino 8",
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
9300: "Cisco-IOS $9$ (scrypt)",
9400: "MS Office 2007",
9500: "MS Office 2010",
9600: "MS Office 2013",
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
9900: "Radmin2",
10000: "Django (PBKDF2-SHA256)",
10100: "SipHash",
10200: "CRAM-MD5",
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
10600: "PDF 1.7 Level 3 (Acrobat 9)",
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
10800: "SHA2-384",
10810: "sha384($pass.$salt)",
10820: "sha384($salt.$pass)",
10830: "sha384(utf16le($pass).$salt)",
10840: "sha384($salt.utf16le($pass))",
10870: "sha384(utf16le($pass))",
10900: "PBKDF2-HMAC-SHA256",
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
11000: "PrestaShop",
11100: "PostgreSQL CRAM (MD5)",
11200: "MySQL CRAM (SHA1)",
11300: "Bitcoin/Litecoin wallet.dat",
11400: "SIP digest authentication (MD5)",
11500: "CRC32",
11600: "7-Zip",
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
11900: "PBKDF2-HMAC-MD5",
12000: "PBKDF2-HMAC-SHA1",
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
12100: "PBKDF2-HMAC-SHA512",
12200: "eCryptfs",
12300: "Oracle T: Type (Oracle 12+)",
12400: "BSDi Crypt, Extended DES",
12500: "RAR3-hp",
12600: "ColdFusion 10+",
12700: "Blockchain, My Wallet",
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
12900: "Android FDE (Samsung DEK)",
13000: "RAR5",
13100: "Kerberos 5, etype 23, TGS-REP",
13200: "AxCrypt 1",
13300: "AxCrypt 1 in-memory SHA1",
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
13500: "PeopleSoft PS_TOKEN",
13600: "WinZip",
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
13800: "Windows Phone 8+ PIN/password",
13900: "OpenCart",
14000: "DES (PT = $salt, key = $pass)",
14100: "3DES (PT = $salt, key = $pass)",
14400: "sha1(CX)",
14500: "Linux Kernel Crypto API (2.4)",
14600: "LUKS",
14700: "iTunes backup < 10.0",
14800: "iTunes backup >= 10.0",
14900: "Skip32 (PT = $salt, key = $pass)",
15000: "FileZilla Server >= 0.9.55",
15100: "Juniper/NetBSD sha1crypt",
15200: "Blockchain, My Wallet, V2",
15300: "DPAPI masterkey file v1 + local context",
15400: "ChaCha20",
15500: "JKS Java Key Store Private Keys (SHA1)",
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
15700: "Ethereum Wallet, SCRYPT",
15900: "DPAPI masterkey file v2 + Active Directory domain context",
16000: "Tripcode",
16100: "TACACS+",
16200: "Apple Secure Notes",
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
16400: "CRAM-MD5 Dovecot",
16500: "JWT (JSON Web Token)",
16600: "Electrum Wallet (Salt-Type 1-3)",
16700: "FileVault 2",
16800: "WPA-PMKID-PBKDF2",
16801: "WPA-PMKID-PMK",
16900: "Ansible Vault",
17200: "PKZIP (Compressed)",
17210: "PKZIP (Uncompressed)",
17220: "PKZIP (Compressed Multi-File)",
17225: "PKZIP (Mixed Multi-File)",
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
17300: "SHA3-224",
17400: "SHA3-256",
17500: "SHA3-384",
17600: "SHA3-512",
17700: "Keccak-224",
17800: "Keccak-256",
17900: "Keccak-384",
18000: "Keccak-512",
18100: "TOTP (HMAC-SHA1)",
18200: "Kerberos 5, etype 23, AS-REP",
18300: "Apple File System (APFS)",
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
18500: "sha1(md5(md5($pass)))",
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
18700: "Java Object hashCode()",
18800: "Blockchain, My Wallet, Second Password (SHA256)",
18900: "Android Backup",
19000: "QNX /etc/shadow (MD5)",
19100: "QNX /etc/shadow (SHA256)",
19200: "QNX /etc/shadow (SHA512)",
19300: "sha1($salt1.$pass.$salt2)",
19500: "Ruby on Rails Restful-Authentication",
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
19800: "Kerberos 5, etype 17, Pre-Auth",
19900: "Kerberos 5, etype 18, Pre-Auth",
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
20200: "Python passlib pbkdf2-sha512",
20300: "Python passlib pbkdf2-sha256",
20400: "Python passlib pbkdf2-sha1",
20500: "PKZIP Master Key",
20510: "PKZIP Master Key (6 byte optimization)",
20600: "Oracle Transportation Management (SHA256)",
20710: "sha256(sha256($pass).$salt)",
20711: "AuthMe sha256",
20720: "sha256($salt.sha256($pass))",
20800: "sha256(md5($pass))",
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
21100: "sha1(md5($pass.$salt))",
21200: "md5(sha1($salt).md5($pass))",
21300: "md5($salt.sha1($salt.$pass))",
21400: "sha256(sha256_bin($pass))",
21500: "SolarWinds Orion",
21501: "SolarWinds Orion v2",
21600: "Web2py pbkdf2-sha512",
21700: "Electrum Wallet (Salt-Type 4)",
21800: "Electrum Wallet (Salt-Type 5)",
22000: "WPA-PBKDF2-PMKID+EAPOL",
22001: "WPA-PMK-PMKID+EAPOL",
22100: "BitLocker",
22200: "Citrix NetScaler (SHA512)",
22300: "sha256($salt.$pass.$salt)",
22301: "Telegram Mobile App Passcode (SHA256)",
22400: "AES Crypt (SHA256)",
22500: "MultiBit Classic .key (MD5)",
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
22700: "MultiBit HD (scrypt)",
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
23001: "SecureZIP AES-128",
23002: "SecureZIP AES-192",
23003: "SecureZIP AES-256",
23100: "Apple Keychain",
23200: "XMPP SCRAM PBKDF2-SHA1",
23300: "Apple iWork",
23400: "Bitwarden",
23500: "AxCrypt 2 AES-128",
23600: "AxCrypt 2 AES-256",
23700: "RAR3-p (Uncompressed)",
23800: "RAR3-p (Compressed)",
23900: "BestCrypt v3 Volume Encryption",
24100: "MongoDB ServerKey SCRAM-SHA-1",
24200: "MongoDB ServerKey SCRAM-SHA-256",
24300: "sha1($salt.sha1($pass.$salt))",
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
24600: "SQLCipher",
24700: "Stuffit5",
24800: "Umbraco HMAC-SHA1",
24900: "Dahua Authentication MD5",
25300: "MS Office 2016 - SheetProtection",
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
25500: "Stargazer Stellar Wallet XLM",
25600: "bcrypt(md5($pass)) / bcryptmd5",
25700: "MurmurHash",
25800: "bcrypt(sha1($pass)) / bcryptsha1",
25900: "KNX IP Secure - Device Authentication Code",
26000: "Mozilla key3.db",
26100: "Mozilla key4.db",
26200: "OpenEdge Progress Encode",
26300: "FortiGate256 (FortiOS256)",
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
26500: "iPhone passcode (UID key + System Keybag)",
26600: "MetaMask Wallet",
99999: "Plaintext",
}
func hashcatHashTypeName(mode int32) (string, bool) {
name, ok := hashcatHashTypes[mode]
return name, ok
}
func humanizeHashRate(rate uint64) string {
const unit = 1000
if rate < unit {
return fmt.Sprintf("%d H/s", rate)
}
value := float64(rate)
exp := 0
for value >= unit && exp < 5 {
value /= unit
exp++
}
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
if exp == 0 {
return fmt.Sprintf("%d H/s", rate)
}
if exp > len(suffixes) {
exp = len(suffixes)
}
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
}
+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 -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",
+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)
+38
View File
@@ -0,0 +1,38 @@
package edit
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the edit command.
func Commands(con *console.SliverClient) []*cobra.Command {
editCmd := &cobra.Command{
Use: consts.EditStr,
Short: "Edit a remote text file",
Long: help.GetHelpFor([]string{consts.EditStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
EditCmd(cmd, con, args)
},
GroupID: consts.FilesystemHelpGroup,
}
flags.Bind("", false, editCmd, func(f *pflag.FlagSet) {
f.String("syntax", "", "syntax highlighting lexer name (optional)")
f.Bool("syntax-select", false, "prompt to select syntax highlighting lexer")
f.Bool("line-numbers", false, "show line numbers")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(editCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to the remote file to edit"),
)
return []*cobra.Command{editCmd}
}
+380
View File
@@ -0,0 +1,380 @@
package edit
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"time"
"unicode/utf8"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
const beaconPollInterval = 1 * time.Second
// EditCmd downloads a remote text file, opens it in a TUI editor, and optionally uploads changes.
func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
remotePath := strings.TrimSpace(args[0])
if remotePath == "" {
con.PrintErrorf("Missing parameter: remote path\n")
return
}
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
download, err := downloadRemote(ctx, con, cmd, remotePath)
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
if !download.Exists {
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
return
}
if download.IsDir {
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
return
}
data := download.Data
if download.Encoder == "gzip" {
data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s\n", err)
return
}
}
if !isText(data) {
con.PrintErrorf("Refusing to edit non-text file: %s\n", download.Path)
return
}
path := download.Path
if path == "" {
path = remotePath
}
lineEnding := detectLineEnding(data)
content := normalizeLineEndings(data)
lexer, lexerName, err := resolveSyntax(cmd, path, string(data))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
showLineNumbers, _ := cmd.Flags().GetBool("line-numbers")
for {
result, err := runEditor(content, path, lexer, lexerName, showLineNumbers)
if err != nil {
con.PrintErrorf("Editor error: %s\n", err)
return
}
content = result.Content
if result.Action == actionNone {
return
}
if !result.Modified {
if result.Action == actionSaveQuit {
con.PrintInfof("No changes to upload for %s\n", path)
}
return
}
switch result.Action {
case actionSaveQuit:
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if !uploadNow {
keepEditing, err := confirm("Continue editing?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if keepEditing {
continue
}
return
}
uploadData := applyLineEnding(content, lineEnding)
if err := uploadEditedContent(cmd, con, path, uploadData); err != nil {
con.PrintErrorf("%s\n", err)
}
return
case actionQuit:
if result.Force {
return
}
discard, err := confirm("Discard changes?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if discard {
return
}
}
}
}
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
program := tea.NewProgram(model, tea.WithAltScreen())
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
}
editor, ok := finalModel.(*editorModel)
if !ok {
return editorResult{}, fmt.Errorf("unexpected editor state")
}
return editor.result(), nil
}
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string) (*sliverpb.Download, error) {
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
RestrictedToFile: true,
})
if err != nil {
return nil, err
}
if download.Response != nil && download.Response.Async {
taskID := download.Response.TaskID
download = &sliverpb.Download{}
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
return nil, err
}
}
return download, nil
}
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
uploadData, err := new(encoders.Gzip).Encode(data)
if err != nil {
return err
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
Data: uploadData,
Encoder: "gzip",
IsDirectory: false,
Overwrite: true,
})
if err == nil && upload.Response != nil && upload.Response.Async {
taskID := upload.Response.TaskID
upload = &sliverpb.Upload{}
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
}
ctrl <- true
<-ctrl
if err != nil {
return err
}
filesystem.PrintUpload(upload, con)
return nil
}
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
task, err := waitForBeaconTask(ctx, con, taskID)
if err != nil {
return err
}
state := strings.ToLower(task.State)
if state != "completed" {
if state == "" {
state = "unknown"
}
return fmt.Errorf("beacon task %s %s", task.ID, state)
}
if len(task.Response) == 0 {
return fmt.Errorf("beacon task %s returned empty response", task.ID)
}
return proto.Unmarshal(task.Response, out)
}
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
ticker := time.NewTicker(beaconPollInterval)
defer ticker.Stop()
for {
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
if err != nil {
return nil, err
}
state := strings.ToLower(task.State)
if state == "completed" || state == "failed" || state == "canceled" {
return task, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
}
}
}
func resolveSyntax(cmd *cobra.Command, path string, content string) (chroma.Lexer, string, error) {
syntaxName, _ := cmd.Flags().GetString("syntax")
selectSyntax, _ := cmd.Flags().GetBool("syntax-select")
syntaxName = strings.TrimSpace(syntaxName)
if strings.EqualFold(syntaxName, "auto") {
syntaxName = ""
}
if syntaxName != "" {
if strings.EqualFold(syntaxName, "none") {
return nil, "none", nil
}
lexer := lexers.Get(syntaxName)
if lexer == nil {
return nil, "", fmt.Errorf("unknown syntax lexer: %s", syntaxName)
}
name := lexer.Config().Name
if name == "" {
name = syntaxName
}
return lexer, name, nil
}
if selectSyntax {
options := syntaxOptions()
selection := "auto"
if err := forms.Select("Syntax highlighting", options, &selection); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return detectSyntax(path, content)
}
return nil, "", err
}
if strings.EqualFold(selection, "none") {
return nil, "none", nil
}
if strings.EqualFold(selection, "auto") {
return detectSyntax(path, content)
}
lexer := lexers.Get(selection)
if lexer == nil {
return nil, "", fmt.Errorf("unknown syntax lexer: %s", selection)
}
name := lexer.Config().Name
if name == "" {
name = selection
}
return lexer, name, nil
}
return detectSyntax(path, content)
}
func detectSyntax(path string, content string) (chroma.Lexer, string, error) {
lexer := lexers.Match(path)
if lexer == nil {
lexer = lexers.Analyse(content)
}
if lexer == nil {
lexer = lexers.Fallback
}
name := lexer.Config().Name
if name == "" {
name = "auto"
}
return lexer, name, nil
}
func syntaxOptions() []string {
names := lexers.Names(true)
sort.Strings(names)
options := make([]string, 0, len(names)+2)
options = append(options, "auto", "none")
options = append(options, names...)
return options
}
func confirm(title string) (bool, error) {
answer := false
if err := forms.Confirm(title, &answer); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return false, nil
}
return false, err
}
return answer, nil
}
func detectLineEnding(data []byte) string {
if strings.Contains(string(data), "\r\n") {
return "\r\n"
}
return "\n"
}
func normalizeLineEndings(data []byte) string {
content := strings.ReplaceAll(string(data), "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")
return content
}
func applyLineEnding(content, lineEnding string) []byte {
if lineEnding == "\r\n" {
content = strings.ReplaceAll(content, "\n", "\r\n")
}
return []byte(content)
}
func isText(sample []byte) bool {
const max = 1024
if len(sample) > max {
sample = sample[:max]
}
for i, c := range string(sample) {
if i+utf8.UTFMax > len(sample) {
break
}
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
return false
}
}
return true
}
File diff suppressed because it is too large. Load diff
+23 -2
View File
@@ -4,6 +4,7 @@ import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/help"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
@@ -25,12 +26,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
f.BoolP("output", "o", false, "capture command output")
f.BoolP("output", "o", true, "capture command output")
f.Bool("background", false, "start the process in the background and track it")
f.BoolP("save", "s", false, "save output to a file")
f.BoolP("loot", "X", false, "save output as loot")
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
f.StringP("name", "n", "", "name to assign loot (optional)")
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
@@ -42,6 +46,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
executeChildrenCmd := &cobra.Command{
Use: consts.ExecuteChildrenStr,
Short: "List tracked background execute child processes",
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
ExecuteChildrenCmd(cmd, con, args)
},
}
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
executeCmd.AddCommand(executeChildrenCmd)
executeAssemblyCmd := &cobra.Command{
Use: consts.ExecuteAssemblyStr,
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
@@ -169,11 +187,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
}
flags.Bind("", false, migrateCmd, func(f *pflag.FlagSet) {
f.BoolP("disable-sgn", "S", true, "disable shikata ga nai shellcode encoder")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.Uint32P("pid", "p", 0, "process id to migrate into")
f.StringP("process-name", "n", "", "name of the process to migrate into")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(migrateCmd, func(comp *carapace.ActionMap) {
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
})
carapace.Gen(migrateCmd).PositionalCompletion(carapace.ActionValues().Usage("PID of process to migrate into"))
msfCmd := &cobra.Command{
+106
View File
@@ -0,0 +1,106 @@
package exec
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
ctrl := make(chan bool)
con.SpinUntil("Querying execute children ...", ctrl)
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
Request: con.ActiveTarget.Request(cmd),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if execChildren.Response != nil && execChildren.Response.Async {
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, execChildren)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecuteChildren(execChildren, con)
})
con.PrintAsyncResponse(execChildren.Response)
return
}
PrintExecuteChildren(execChildren, con)
}
// PrintExecuteChildren - Render execute children output.
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
if execChildren == nil || len(execChildren.Children) == 0 {
con.PrintInfof("No tracked background execute children\n")
return
}
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
sort.Slice(children, func(i, j int) bool {
return children[i].StartTime < children[j].StartTime
})
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
for _, child := range children {
state := "running"
exitCode := "-"
if child.Exited {
state = "exited"
exitCode = fmt.Sprintf("%d", child.ExitCode)
}
started := "-"
if child.StartTime != 0 {
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
}
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
}
con.Printf("%s\n", tw.Render())
}
+21
View File
@@ -157,10 +157,25 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
var rows uint32
var cols uint32
if !noPty {
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || rowsInt <= 0 || colsInt <= 0 {
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
}
if err == nil && rowsInt > 0 && colsInt > 0 {
rows = uint32(rowsInt)
cols = uint32(colsInt)
}
}
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(cmd),
Path: hostProc,
EnablePTY: !noPty,
Rows: rows,
Cols: cols,
TunnelID: tunnel.ID,
})
if err != nil {
@@ -200,6 +215,12 @@ func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, r
}
}
stopPtyResize := func() {}
if !noPty {
stopPtyResize = startPtyResizeWatcher(con, cmd, tunnel.ID)
}
defer stopPtyResize()
log.Printf("Starting stdin/stdout shell ...")
go func() {
n, err := io.Copy(os.Stdout, tunnel)
+61 -13
View File
@@ -44,35 +44,53 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
token, _ := cmd.Flags().GetBool("token")
hidden, _ := cmd.Flags().GetBool("hidden")
output, _ := cmd.Flags().GetBool("output")
background, _ := cmd.Flags().GetBool("background")
stdout, _ := cmd.Flags().GetString("stdout")
stderr, _ := cmd.Flags().GetString("stderr")
saveLoot, _ := cmd.Flags().GetBool("loot")
saveOutput, _ := cmd.Flags().GetBool("save")
ppid, _ := cmd.Flags().GetUint32("ppid")
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
envPairs, _ := cmd.Flags().GetStringArray("env")
envVars, err := parseEnvPairs(envPairs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
hostName := getHostname(session, beacon)
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
captureOutput := output || saveLoot || saveOutput
if background && (saveLoot || saveOutput) {
con.PrintErrorf("The loot and save options are not supported with --background\n")
return
}
if output && beacon != nil {
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
captureOutput := !background && (output || saveLoot || saveOutput)
if captureOutput && beacon != nil {
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
}
var exec *sliverpb.Execute
var err error
ctrl := make(chan bool)
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
if token || hidden || ppid != 0 {
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
return
}
if envInheritance || len(envVars) > 0 {
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
return
}
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Background: background,
Stderr: stderr,
Stdout: stdout,
UseToken: token,
@@ -81,12 +99,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
})
} else {
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Background: background,
Stderr: stderr,
Stdout: stdout,
EnvInheritance: envInheritance,
Env: envVars,
})
}
ctrl <- true
@@ -141,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
stderr, _ := cmd.Flags().GetString("stderr")
output, _ := cmd.Flags().GetBool("output")
background := false
if cmd.Flags().Lookup("background") != nil {
background, _ = cmd.Flags().GetBool("background")
}
if background {
// Background execution never returns inline output.
output = false
}
if !output {
if exec.Status == 0 {
con.PrintInfof("Command executed successfully\n")
if exec.Pid != 0 {
if background {
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
return
}
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
} else {
con.PrintErrorf("Exit code %d\n", exec.Status)
con.PrintInfof("Process started\n")
}
return
}
@@ -179,6 +212,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
return ""
}
func parseEnvPairs(envPairs []string) (map[string]string, error) {
if len(envPairs) == 0 {
return nil, nil
}
envVars := make(map[string]string, len(envPairs))
for _, pair := range envPairs {
parts := strings.SplitN(pair, "=", 2)
if len(parts) != 2 || parts[0] == "" {
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
}
envVars[parts[0]] = parts[1]
}
return envVars, nil
}
func determineCommandName(command string) string {
commandName := strings.ReplaceAll(command, "\\", "/")
+131 -3
View File
@@ -20,14 +20,19 @@ package exec
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
// MigrateCmd - Windows only, inject an implant into another process
@@ -55,9 +60,17 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
implantName = beacon.Name
}
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
if disableSgn, _ := cmd.Flags().GetBool("disable-sgn"); disableSgn {
encoder = clientpb.ShellcodeEncoder_NONE
targetArch := config.GetGOARCH()
if session != nil && session.Arch != "" {
targetArch = session.Arch
} else if beacon != nil && beacon.Arch != "" {
targetArch = beacon.Arch
}
encoder, err := parseShellcodeEncoderFlag(cmd, targetArch, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
ctrl := make(chan bool)
@@ -120,3 +133,118 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("Successfully migrated to %d\n", migrate.Pid)
}
}
func normalizeShellcodeEncoderName(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "_")
return normalized
}
func normalizeShellcodeArch(arch string) string {
normalized := strings.ToLower(strings.TrimSpace(arch))
switch normalized {
case "amd64", "x64", "x86_64":
return "amd64"
case "386", "x86", "i386":
return "386"
case "arm64", "aarch64":
return "arm64"
default:
return normalized
}
}
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
if con == nil || con.Rpc == nil {
return nil, errors.New("no RPC client")
}
grpcCtx, cancel := con.GrpcContext(nil)
defer cancel()
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
}
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
if encoderMap == nil {
return nil
}
arch = normalizeShellcodeArch(arch)
archMap := encoderMap.GetEncoders()[arch]
if archMap == nil {
return nil
}
names := make([]string, 0, len(archMap.GetEncoders()))
for name := range archMap.GetEncoders() {
names = append(names, name)
}
sort.Strings(names)
return names
}
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
if encoderMap == nil {
return clientpb.ShellcodeEncoder_NONE, false
}
arch = normalizeShellcodeArch(arch)
name = normalizeShellcodeEncoderName(name)
archMap := encoderMap.GetEncoders()[arch]
if archMap == nil {
return clientpb.ShellcodeEncoder_NONE, false
}
encoder, ok := archMap.GetEncoders()[name]
return encoder, ok
}
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
rawEncoder = strings.TrimSpace(rawEncoder)
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
}
normalized := normalizeShellcodeEncoderName(rawEncoder)
if normalized == "none" {
return clientpb.ShellcodeEncoder_NONE, nil
}
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return clientpb.ShellcodeEncoder_NONE, err
}
// If no encoder specified, prompt for a compatible encoder (or none).
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
if len(compatible) == 0 {
// No known compatible encoders for this arch.
return clientpb.ShellcodeEncoder_NONE, nil
}
options := append([]string{"none"}, compatible...)
choice := "none"
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
return clientpb.ShellcodeEncoder_NONE, err
}
if normalizeShellcodeEncoderName(choice) == "none" {
return clientpb.ShellcodeEncoder_NONE, nil
}
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
if !ok {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
}
return encoder, nil
}
// Encoder explicitly specified by name.
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
if !ok {
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
if len(compatible) == 0 {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
}
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
}
return encoder, nil
}
+97
View File
@@ -0,0 +1,97 @@
//go:build !windows
package exec
import (
"context"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
if con == nil || con.Rpc == nil {
return func() {}
}
stdoutFd := int(os.Stdout.Fd())
stdinFd := int(os.Stdin.Fd())
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
return func() {}
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGWINCH)
stopCh := make(chan struct{})
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
lastCols, lastRows := -1, -1
send := func() bool {
cols, rows, err := term.GetSize(stdoutFd)
if err != nil || cols <= 0 || rows <= 0 {
cols, rows, err = term.GetSize(stdinFd)
}
if err != nil || cols <= 0 || rows <= 0 {
return true
}
if cols == lastCols && rows == lastRows {
return true
}
lastCols, lastRows = cols, rows
req := con.ActiveTarget.Request(cmd)
if req == nil {
return false
}
req.Timeout = int64(2 * time.Second)
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
Request: req,
TunnelID: tunnelID,
Rows: uint32(rows),
Cols: uint32(cols),
})
if status.Code(err) == codes.Unimplemented {
return false
}
return true
}
if !send() {
return
}
for {
select {
case <-stopCh:
return
case <-sigCh:
if !send() {
return
}
}
}
}()
var once sync.Once
return func() {
once.Do(func() {
signal.Stop(sigCh)
close(stopCh)
<-doneCh
})
}
}
+93
View File
@@ -0,0 +1,93 @@
//go:build windows
package exec
import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"os"
"sync"
"time"
)
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
if con == nil || con.Rpc == nil {
return func() {}
}
stdoutFd := int(os.Stdout.Fd())
stdinFd := int(os.Stdin.Fd())
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
return func() {}
}
stopCh := make(chan struct{})
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
lastCols, lastRows := -1, -1
send := func() bool {
cols, rows, err := term.GetSize(stdoutFd)
if err != nil || cols <= 0 || rows <= 0 {
cols, rows, err = term.GetSize(stdinFd)
}
if err != nil || cols <= 0 || rows <= 0 {
return true
}
if cols == lastCols && rows == lastRows {
return true
}
lastCols, lastRows = cols, rows
req := con.ActiveTarget.Request(cmd)
if req == nil {
return false
}
req.Timeout = int64(2 * time.Second)
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
Request: req,
TunnelID: tunnelID,
Rows: uint32(rows),
Cols: uint32(cols),
})
if status.Code(err) == codes.Unimplemented {
return false
}
return true
}
if !send() {
return
}
for {
select {
case <-stopCh:
return
case <-ticker.C:
if !send() {
return
}
}
}
}()
var once sync.Once
return func() {
once.Do(func() {
close(stopCh)
<-doneCh
})
}
}
+83 -94
View File
@@ -30,7 +30,6 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/assets"
@@ -43,8 +42,8 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
appConsole "github.com/reeflective/console"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/protobuf/proto"
@@ -121,6 +120,7 @@ type extensionArgument struct {
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
@@ -689,94 +689,6 @@ func PrintExtOutput(extName string, commandName string, outputSchema *packages.O
}
}
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
var err error
argsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
// Parse BOF arguments from grumble
missingRequiredArgs := make([]string, 0)
// If we have an extension that expects a single string, but more than one has been parsed, combine them
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
// The loop below will only read the first element of args because ext.Arguments is 1
args[0] = strings.Join(args, " ")
}
for _, arg := range ext.Arguments {
// If we don't have any positional words left to consume,
// add the remaining required extension arguments in the
// error message.
if len(args) == 0 {
if !arg.Optional {
missingRequiredArgs = append(missingRequiredArgs, "`"+arg.Name+"`")
}
continue
}
// Else pop a word from the list
word := args[0]
args = args[1:]
switch arg.Type {
case "integer":
fallthrough
case "int":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddInt(uint32(val))
if err != nil {
return nil, err
}
case "short":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddShort(uint16(val))
if err != nil {
return nil, err
}
case "string":
err = argsBuffer.AddString(word)
if err != nil {
return nil, err
}
case "wstring":
err = argsBuffer.AddWString(word)
if err != nil {
return nil, err
}
// Adding support for filepaths so we can
// send binary data like shellcodes to BOFs
case "file":
data, err := os.ReadFile(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddData(data)
if err != nil {
return nil, err
}
}
}
// Return if we have missing required arguments
if len(missingRequiredArgs) > 0 {
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
}
parsedArgs, err := argsBuffer.GetBuffer()
if err != nil {
return nil, err
}
return parsedArgs, nil
}
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
var extensionArgs []byte
binData, err := os.ReadFile(binPath)
@@ -837,16 +749,27 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
return nil
}
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
// makeExtensionArgCompleter builds the positional and dash arguments completer for the extension.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --process, --shellcode)
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
var actions []carapace.Action
for _, arg := range extCmd.Arguments {
var action carapace.Action
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
@@ -858,6 +781,72 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// The extension arguments are parsed as flags (--name value) by argparser.go
if len(extCmd.Arguments) > 0 {
// Pre-build the flag completions at registration time (not in a callback)
// This ensures the values are captured correctly
flagCompletion := makeExtensionFlagNameCompletion(extCmd)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range extCmd.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
}
// makeExtensionFlagNameCompletion creates completion for flag names
func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
var results []string
for _, arg := range extCmd.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("extension arguments")
}
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
+77 -10
View File
@@ -19,6 +19,8 @@ package filesystem
import (
"context"
"fmt"
"strconv"
"time"
"github.com/bishopfox/sliver/client/console"
@@ -28,14 +30,77 @@ import (
"google.golang.org/protobuf/proto"
)
const chtimesDefaultLayout = "2006-01-02 15:04:05"
type chtimesTimeFormat struct {
name string
parse func(string) (int64, error)
}
func chtimesParseUnixSeconds(value string) (int64, error) {
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
return parsed, nil
}
func chtimesParseUnixMillis(value string) (int64, error) {
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
return time.UnixMilli(parsed).Unix(), nil
}
func chtimesParseLayout(layout string) func(string) (int64, error) {
return func(value string) (int64, error) {
parsed, err := time.Parse(layout, value)
if err != nil {
return 0, err
}
return parsed.Unix(), nil
}
}
func chtimesFormatFromFlags(cmd *cobra.Command) (chtimesTimeFormat, error) {
formatFlags := []struct {
flag string
format chtimesTimeFormat
}{
{flag: "unix", format: chtimesTimeFormat{name: "unix", parse: chtimesParseUnixSeconds}},
{flag: "unix-ms", format: chtimesTimeFormat{name: "unix-ms", parse: chtimesParseUnixMillis}},
{flag: "rfc3339", format: chtimesTimeFormat{name: "rfc3339", parse: chtimesParseLayout(time.RFC3339)}},
{flag: "rfc1123", format: chtimesTimeFormat{name: "rfc1123", parse: chtimesParseLayout(time.RFC1123)}},
}
var selected *chtimesTimeFormat
selectedFlag := ""
for _, candidate := range formatFlags {
enabled, _ := cmd.Flags().GetBool(candidate.flag)
if !enabled {
continue
}
if selected != nil {
return chtimesTimeFormat{}, fmt.Errorf("only one time format flag can be used (--%s and --%s are both set)", selectedFlag, candidate.flag)
}
selected = &candidate.format
selectedFlag = candidate.flag
}
if selected == nil {
return chtimesTimeFormat{name: "datetime", parse: chtimesParseLayout(chtimesDefaultLayout)}, nil
}
return *selected, nil
}
// ChtimesCmd - Change the access and modified time of a file on the remote file system.
func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
// DateTime layout (https://pkg.go.dev/time)
layout := "2006-01-02 15:04:05"
filePath := args[0]
if filePath == "" {
@@ -43,33 +108,35 @@ func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
return
}
atime := args[1]
format, err := chtimesFormatFromFlags(cmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
atime := args[1]
if atime == "" {
con.PrintErrorf("Missing parameter: Last accessed time id\n")
return
}
t_a, err := time.Parse(layout, atime)
unixAtime, err := format.parse(atime)
if err != nil {
con.PrintErrorf("%s\n", err)
con.PrintErrorf("Invalid access time (%s): %s\n", format.name, err)
return
}
unixAtime := t_a.Unix()
mtime := args[2]
if mtime == "" {
con.PrintErrorf("Missing parameter: Last modified time id\n")
return
}
t_b, err := time.Parse(layout, mtime)
unixMtime, err := format.parse(mtime)
if err != nil {
con.PrintErrorf("%s\n", err)
con.PrintErrorf("Invalid modified time (%s): %s\n", format.name, err)
return
}
unixMtime := t_b.Unix()
chtimes, err := con.Rpc.Chtimes(context.Background(), &sliverpb.ChtimesReq{
Request: con.ActiveTarget.Request(cmd),
+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 {
+8 -8
View File
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Sliver Name",
"Implant Name",
"Domain",
"Triggered",
"First Trigger",
@@ -42,20 +42,20 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
if burnedOnly && !canary.Triggered {
continue
}
lineColor := console.Normal
lineStyle := console.StyleNormal
if canary.Triggered {
lineColor = console.Bold + console.Red
lineStyle = console.StyleBoldRed
}
firstTrigger := "Never"
latestTrigger := "Never"
if canary.Triggered {
firstTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.FirstTriggered)
latestTrigger = fmt.Sprintf(lineColor+"%s"+console.Normal, canary.LatestTrigger)
firstTrigger = lineStyle.Render(canary.FirstTriggered)
latestTrigger = lineStyle.Render(canary.LatestTrigger)
}
row := table.Row{
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.ImplantName),
fmt.Sprintf(lineColor+"%s"+console.Normal, canary.Domain),
fmt.Sprintf(lineColor+"%v"+console.Normal, canary.Triggered),
lineStyle.Render(canary.ImplantName),
lineStyle.Render(canary.Domain),
lineStyle.Render(fmt.Sprintf("%v", canary.Triggered)),
firstTrigger,
latestTrigger,
}
+46 -3
View File
@@ -3,6 +3,7 @@ package generate
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
@@ -142,12 +143,13 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("profiles", false, profilesGenerateCmd, func(f *pflag.FlagSet) {
f.StringP("save", "s", "", "directory/file to the binary to")
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.StringP("name", "n", "", "Implant name")
})
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
})
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
profilesCmd.AddCommand(profilesGenerateCmd)
@@ -308,7 +310,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("debug-file", "O", "", "path to debug output")
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
f.StringP("canary", "c", "", "canary domain(s)")
@@ -341,7 +343,46 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
// Shellcode generation options:
// - Windows: Donut
// - macOS: beignet
// - Linux: malasada
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows, macOS, and Linux shellcode)")
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows shellcode only)")
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows shellcode only)")
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows shellcode only)")
// Backwards-compatible deprecated Donut flags (hidden).
f.Uint32("donut-entropy", 1, "Deprecated (use --shellcode-entropy)")
_ = f.MarkDeprecated("donut-entropy", "use --shellcode-entropy")
_ = f.MarkHidden("donut-entropy")
f.Bool("donut-compress", false, "Deprecated (use --shellcode-compress)")
_ = f.MarkDeprecated("donut-compress", "use --shellcode-compress")
_ = f.MarkHidden("donut-compress")
f.Uint32("donut-exitopt", 1, "Deprecated (use --shellcode-exitopt)")
_ = f.MarkDeprecated("donut-exitopt", "use --shellcode-exitopt")
_ = f.MarkHidden("donut-exitopt")
f.Uint32("donut-bypass", 3, "Deprecated (use --shellcode-bypass)")
_ = f.MarkDeprecated("donut-bypass", "use --shellcode-bypass")
_ = f.MarkHidden("donut-bypass")
f.Uint32("donut-headers", 1, "Deprecated (use --shellcode-headers)")
_ = f.MarkDeprecated("donut-headers", "use --shellcode-headers")
_ = f.MarkHidden("donut-headers")
f.Bool("donut-thread", false, "Deprecated (use --shellcode-thread)")
_ = f.MarkDeprecated("donut-thread", "use --shellcode-thread")
_ = f.MarkHidden("donut-thread")
f.Bool("donut-unicode", false, "Deprecated (use --shellcode-unicode)")
_ = f.MarkDeprecated("donut-unicode", "use --shellcode-unicode")
_ = f.MarkHidden("donut-unicode")
f.Uint32("donut-oep", 0, "Deprecated (use --shellcode-oep)")
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
_ = f.MarkHidden("donut-oep")
})
}
@@ -354,9 +395,11 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
(*comp)["format"] = FormatCompleter()
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
(*comp)["c2profile"] = HTTPC2Completer(con)
})
registerImplantTargetFlagCompletions(cmd, con)
}
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
+2 -1
View File
@@ -22,7 +22,8 @@ var (
// GenerateBeaconCmd - The main command used to generate implant binaries
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateBeaconForm(cmd, con, args) {
result, err := forms.GenerateBeaconForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateBeaconForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
+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))
}
+399 -33
View File
@@ -28,6 +28,7 @@ import (
"os/signal"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"time"
@@ -79,6 +80,7 @@ var (
"darwin/arm64": true,
"linux/386": true,
"linux/amd64": true,
"linux/arm64": true,
"windows/386": true,
"windows/amd64": true,
}
@@ -91,7 +93,8 @@ var (
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
result, err := forms.GenerateForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
@@ -118,14 +121,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} else {
_, err := externalBuild(name, config, save, con)
if err != nil {
if err == ErrNoExternalBuilder {
switch err {
case ErrNoExternalBuilder:
con.PrintErrorf("There are no external builders currently connected to the server\n")
con.PrintErrorf("See 'builders' command for more information\n")
} else if err == ErrNoValidBuilders {
case ErrNoValidBuilders:
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
con.PrintErrorf("See 'builders' command for more information\n")
} else {
default:
con.PrintErrorf("%s\n", err)
}
return
@@ -351,7 +355,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
isSharedLib := false
isService := false
isShellcode := false
sgnEnabled := false
var exports []string
exportsArg, _ := cmd.Flags().GetString("exports")
@@ -375,8 +378,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
case "shellcode":
configFormat = clientpb.OutputFormat_SHELLCODE
isShellcode = true
sgnEnabled, _ = cmd.Flags().GetBool("disable-sgn")
sgnEnabled = !sgnEnabled
case "service":
configFormat = clientpb.OutputFormat_SERVICE
isService = true
@@ -393,9 +394,28 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
if targetOS == "" || targetArch == "" {
return "", nil
}
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
return "", nil
if configFormat == clientpb.OutputFormat_SHELLCODE {
switch targetOS {
case "windows":
// Server-side implementation supports amd64 and 386 only.
if targetArch != "amd64" && targetArch != "386" {
con.PrintErrorf("Windows shellcode format is only supported on windows/amd64 and windows/386\n")
return "", nil
}
case "darwin":
if targetArch != "arm64" {
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
return "", nil
}
case "linux":
if targetArch != "amd64" && targetArch != "arm64" {
con.PrintErrorf("Linux shellcode format is only supported on linux/amd64 and linux/arm64\n")
return "", nil
}
default:
con.PrintErrorf("Shellcode format is currently only supported on Windows, macOS, and Linux\n")
return "", nil
}
}
if len(namedPipeC2) > 0 && targetOS != "windows" {
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
@@ -407,6 +427,19 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
return "", nil
}
shellcodeConfig, err := parseShellcodeFlags(cmd, targetOS, configFormat, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return "", nil
}
shellcodeEncoder, err := parseShellcodeEncoderFlag(cmd, targetArch, configFormat, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return "", nil
}
sgnEnabled := shellcodeEncoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI // legacy field
var tunIP net.IP
if wg, _ := cmd.Flags().GetString("wg"); wg != "" {
uniqueWGIP, err := con.Rpc.GenerateUniqueIP(context.Background(), &commonpb.Empty{})
@@ -444,6 +477,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
Debug: debug,
Evasion: evasion,
SGNEnabled: sgnEnabled,
ShellcodeEncoder: shellcodeEncoder,
ObfuscateSymbols: symbolObfuscation,
C2: c2s,
CanaryDomains: canaryDomains,
@@ -478,11 +512,272 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
DebugFile: debugFile,
HTTPC2ConfigName: c2Profile,
ShellcodeConfig: shellcodeConfig,
}
return name, config
}
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
if !cmd.Flags().Changed("shellcode-entropy") {
shellcodeEntropy, _ = cmd.Flags().GetUint32("donut-entropy")
}
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
if !cmd.Flags().Changed("shellcode-compress") {
shellcodeCompressEnabled, _ = cmd.Flags().GetBool("donut-compress")
}
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
if !cmd.Flags().Changed("shellcode-exitopt") {
shellcodeExitOpt, _ = cmd.Flags().GetUint32("donut-exitopt")
}
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
if !cmd.Flags().Changed("shellcode-bypass") {
shellcodeBypass, _ = cmd.Flags().GetUint32("donut-bypass")
}
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
if !cmd.Flags().Changed("shellcode-headers") {
shellcodeHeaders, _ = cmd.Flags().GetUint32("donut-headers")
}
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
if !cmd.Flags().Changed("shellcode-thread") {
shellcodeThread, _ = cmd.Flags().GetBool("donut-thread")
}
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
if !cmd.Flags().Changed("shellcode-unicode") {
shellcodeUnicode, _ = cmd.Flags().GetBool("donut-unicode")
}
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
if !cmd.Flags().Changed("shellcode-oep") {
shellcodeOEP, _ = cmd.Flags().GetUint32("donut-oep")
}
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
cmd.Flags().Changed("shellcode-compress") ||
cmd.Flags().Changed("shellcode-exitopt") ||
cmd.Flags().Changed("shellcode-bypass") ||
cmd.Flags().Changed("shellcode-headers") ||
cmd.Flags().Changed("shellcode-thread") ||
cmd.Flags().Changed("shellcode-unicode") ||
cmd.Flags().Changed("shellcode-oep") ||
cmd.Flags().Changed("donut-entropy") ||
cmd.Flags().Changed("donut-compress") ||
cmd.Flags().Changed("donut-exitopt") ||
cmd.Flags().Changed("donut-bypass") ||
cmd.Flags().Changed("donut-headers") ||
cmd.Flags().Changed("donut-thread") ||
cmd.Flags().Changed("donut-unicode") ||
cmd.Flags().Changed("donut-oep")
if configFormat != clientpb.OutputFormat_SHELLCODE {
if anyChanged {
con.PrintWarnf("Shellcode options only apply when using `--format shellcode`, ignoring.\n")
}
return nil, nil
}
windowsOnlyChanged := cmd.Flags().Changed("shellcode-entropy") ||
cmd.Flags().Changed("shellcode-exitopt") ||
cmd.Flags().Changed("shellcode-bypass") ||
cmd.Flags().Changed("shellcode-headers") ||
cmd.Flags().Changed("shellcode-thread") ||
cmd.Flags().Changed("shellcode-unicode") ||
cmd.Flags().Changed("shellcode-oep") ||
cmd.Flags().Changed("donut-entropy") ||
cmd.Flags().Changed("donut-exitopt") ||
cmd.Flags().Changed("donut-bypass") ||
cmd.Flags().Changed("donut-headers") ||
cmd.Flags().Changed("donut-thread") ||
cmd.Flags().Changed("donut-unicode") ||
cmd.Flags().Changed("donut-oep")
// macOS and Linux shellcode currently only support compression.
if targetOS != "windows" {
if targetOS != "darwin" && targetOS != "linux" {
// parseCompileFlags normally blocks this, but keep a safe fallback.
if anyChanged {
con.PrintWarnf("Shellcode options are only supported on Windows, macOS, and Linux shellcode, ignoring.\n")
}
return nil, nil
}
if windowsOnlyChanged {
if targetOS == "darwin" {
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
} else {
con.PrintWarnf("Windows-only shellcode options are ignored on Linux shellcode.\n")
}
}
shellcodeCompress := uint32(1)
if shellcodeCompressEnabled {
shellcodeCompress = 2
}
return &clientpb.ShellcodeConfig{
Compress: shellcodeCompress,
}, nil
}
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
return nil, fmt.Errorf("shellcode-entropy must be between 1 and 3")
}
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
return nil, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
}
if shellcodeBypass < 1 || shellcodeBypass > 3 {
return nil, fmt.Errorf("shellcode-bypass must be between 1 and 3")
}
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
return nil, fmt.Errorf("shellcode-headers must be 1 or 2")
}
shellcodeCompress := uint32(1)
if shellcodeCompressEnabled {
shellcodeCompress = 2
}
return &clientpb.ShellcodeConfig{
Entropy: shellcodeEntropy,
Compress: shellcodeCompress,
ExitOpt: shellcodeExitOpt,
Bypass: shellcodeBypass,
Headers: shellcodeHeaders,
Thread: shellcodeThread,
Unicode: shellcodeUnicode,
OEP: shellcodeOEP,
}, nil
}
func normalizeShellcodeEncoderName(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "_")
return normalized
}
func normalizeShellcodeArch(arch string) string {
normalized := strings.ToLower(strings.TrimSpace(arch))
switch normalized {
case "amd64", "x64", "x86_64":
return "amd64"
case "386", "x86", "i386":
return "386"
case "arm64", "aarch64":
return "arm64"
default:
return normalized
}
}
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
if con == nil || con.Rpc == nil {
return nil, errors.New("no RPC client")
}
grpcCtx, cancel := con.GrpcContext(nil)
defer cancel()
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
}
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
if encoderMap == nil {
return nil
}
arch = normalizeShellcodeArch(arch)
archMap := encoderMap.GetEncoders()[arch]
if archMap == nil {
return nil
}
names := make([]string, 0, len(archMap.GetEncoders()))
for name := range archMap.GetEncoders() {
names = append(names, name)
}
sort.Strings(names)
return names
}
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
if encoderMap == nil {
return clientpb.ShellcodeEncoder_NONE, false
}
arch = normalizeShellcodeArch(arch)
name = normalizeShellcodeEncoderName(name)
archMap := encoderMap.GetEncoders()[arch]
if archMap == nil {
return clientpb.ShellcodeEncoder_NONE, false
}
encoder, ok := archMap.GetEncoders()[name]
return encoder, ok
}
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, configFormat clientpb.OutputFormat, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
rawEncoder = strings.TrimSpace(rawEncoder)
if configFormat != clientpb.OutputFormat_SHELLCODE {
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder != "" {
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
}
return clientpb.ShellcodeEncoder_NONE, nil
}
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
}
normalized := normalizeShellcodeEncoderName(rawEncoder)
if normalized == "none" {
return clientpb.ShellcodeEncoder_NONE, nil
}
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return clientpb.ShellcodeEncoder_NONE, err
}
// If no encoder specified, prompt for a compatible encoder (or none).
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
if len(compatible) == 0 {
// No known compatible encoders for this arch.
return clientpb.ShellcodeEncoder_NONE, nil
}
options := append([]string{"none"}, compatible...)
choice := "none"
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
return clientpb.ShellcodeEncoder_NONE, err
}
if normalizeShellcodeEncoderName(choice) == "none" {
return clientpb.ShellcodeEncoder_NONE, nil
}
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
if !ok {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
}
return encoder, nil
}
// Encoder explicitly specified by name.
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
if !ok {
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
if len(compatible) == 0 {
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
}
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
}
return encoder, nil
}
// parseTrafficEncoderArgs - parses the traffic encoder args and returns a bool indicating if traffic encoders are enabled.
func parseTrafficEncoderArgs(cmd *cobra.Command, httpC2Enabled bool, con *console.SliverClient) (bool, []*commonpb.File) {
trafficEncoders, _ := cmd.Flags().GetString("traffic-encoders")
@@ -525,9 +820,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)
@@ -877,9 +1170,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()
@@ -961,12 +1254,58 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
}
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
fileData := generated.File.Data
if config.IsShellcode {
encoder := config.ShellcodeEncoder
legacySGN := false
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
legacySGN = true
}
if encoder != clientpb.ShellcodeEncoder_NONE {
encoderName := map[clientpb.ShellcodeEncoder]string{
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
clientpb.ShellcodeEncoder_XOR: "xor",
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
}[encoder]
if encoderName == "" {
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
}
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return nil, err
}
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
if legacySGN {
con.PrintWarnf("%s, skipping.\n", msg)
} else {
return nil, errors.New(msg)
}
} else {
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
Encoder: encoder,
Architecture: config.GOARCH,
Iterations: 1,
BadChars: []byte{},
Data: fileData,
})
if err != nil {
return nil, err
}
con.Printf("success!\n")
fileData = resp.GetData()
}
}
}
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
if err != nil {
return nil, err
}
err = os.WriteFile(saveTo, generated.File.Data, 0o700)
err = os.WriteFile(saveTo, fileData, 0o700)
if err != nil {
con.PrintErrorf("Failed to write to: %s\n", saveTo)
return nil, err
@@ -984,9 +1323,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()
@@ -1013,22 +1352,49 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
fileData := generated.File.Data
if config.IsShellcode {
if !config.SGNEnabled {
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
} else {
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
Architecture: config.GOARCH,
Iterations: 1,
BadChars: []byte{},
Data: fileData,
})
if err != nil {
con.PrintErrorf("%s\n", err)
encoder := config.ShellcodeEncoder
legacySGN := false
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
legacySGN = true
}
if encoder != clientpb.ShellcodeEncoder_NONE {
encoderName := map[clientpb.ShellcodeEncoder]string{
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
clientpb.ShellcodeEncoder_XOR: "xor",
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
}[encoder]
if encoderName == "" {
con.PrintWarnf("Unknown shellcode encoder enum %d, skipping.\n", int32(encoder))
} else {
con.Printf("success!\n")
fileData = resp.GetData()
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return nil, err
}
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
if legacySGN {
con.PrintWarnf("%s, skipping.\n", msg)
} else {
con.PrintErrorf("%s\n", msg)
return nil, errors.New(msg)
}
} else {
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
Encoder: encoder,
Architecture: config.GOARCH,
Iterations: 1,
BadChars: []byte{},
Data: fileData,
})
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.Printf("success!\n")
fileData = resp.GetData()
}
}
}
}
}
+134 -60
View File
@@ -2,12 +2,15 @@ package generate
import (
"context"
"errors"
"fmt"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// GetSliverBinary - Get the binary of an implant based on it's profile.
@@ -28,19 +31,49 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
}
data = generated.GetFile().GetData()
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE && profile.Config.SGNEnabled {
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
Architecture: profile.Config.GOARCH,
Iterations: 1,
BadChars: []byte{},
Data: data,
})
if err != nil {
con.PrintErrorf("Error encoding shellcode")
return nil, err
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE {
encoder := profile.Config.ShellcodeEncoder
legacySGN := false
if encoder == clientpb.ShellcodeEncoder_NONE && profile.Config.SGNEnabled {
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
legacySGN = true
}
if encoder != clientpb.ShellcodeEncoder_NONE {
encoderName := map[clientpb.ShellcodeEncoder]string{
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
clientpb.ShellcodeEncoder_XOR: "xor",
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
}[encoder]
if encoderName == "" {
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
}
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return nil, err
}
if _, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, encoderName); !ok {
msg := fmt.Sprintf("shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(profile.Config.GOARCH))
if legacySGN {
con.PrintWarnf("%s, skipping.\n", msg)
} else {
return nil, errors.New(msg)
}
} else {
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
Encoder: encoder,
Architecture: profile.Config.GOARCH,
Iterations: 1,
BadChars: []byte{},
Data: data,
})
if err != nil {
con.PrintErrorf("Error encoding shellcode")
return nil, err
}
data = encodeResp.Data
}
}
data = encodeResp.Data
}
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
@@ -51,71 +84,112 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
return data, err
}
func compilerTargets(con *console.SliverClient) (*clientpb.Compiler, error) {
if con == nil || con.Rpc == nil {
return nil, fmt.Errorf("no compiler info")
}
return con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
}
func appendUnique(values []string, value string) []string {
for _, existing := range values {
if existing == value {
return values
}
}
return append(values, value)
}
func compilerArchValues(compiler *clientpb.Compiler) []string {
results := []string{}
for _, target := range compiler.Targets {
results = appendUnique(results, target.GOARCH)
}
for _, target := range compiler.UnsupportedTargets {
results = appendUnique(results, target.GOARCH)
}
return results
}
func compilerOSValues(compiler *clientpb.Compiler) []string {
results := []string{}
for _, target := range compiler.Targets {
results = appendUnique(results, target.GOOS)
}
for _, target := range compiler.UnsupportedTargets {
results = appendUnique(results, target.GOOS)
}
return results
}
func filterByPrefix(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
if strings.HasPrefix(value, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
func registerTargetFlagCompletion(cmd *cobra.Command, name string, valuesFn func(*clientpb.Compiler) []string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
compiler, err := compilerTargets(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
values := valuesFn(compiler)
return filterByPrefix(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func registerImplantTargetFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
registerTargetFlagCompletion(cmd, "os", compilerOSValues, con)
registerTargetFlagCompletion(cmd, "arch", compilerArchValues, con)
}
// FormatCompleter completes builds' architectures.
func ArchCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
compiler, err := compilerTargets(con)
if err != nil {
return carapace.ActionMessage("No compiler info: %s", err.Error())
}
var results []string
nextTarget:
for _, target := range compiler.Targets {
for _, res := range results {
if res == target.GOARCH {
continue nextTarget
}
}
results = append(results, target.GOARCH)
}
nextUnsupported:
for _, target := range compiler.UnsupportedTargets {
for _, res := range results {
if res == target.GOARCH {
continue nextUnsupported
}
}
results = append(results, target.GOARCH)
}
return carapace.ActionValues(results...).Tag("architectures")
return carapace.ActionValues(compilerArchValues(compiler)...).Tag("architectures")
})
}
// FormatCompleter completes build operating systems.
func OSCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
compiler, err := compilerTargets(con)
if err != nil {
return carapace.ActionMessage("No compiler info: %s", err.Error())
}
var results []string
nextTarget:
for _, target := range compiler.Targets {
for _, res := range results {
if res == target.GOOS {
continue nextTarget
}
}
results = append(results, target.GOOS)
}
nextUnsupported:
for _, target := range compiler.UnsupportedTargets {
for _, res := range results {
if res == target.GOOS {
continue nextUnsupported
}
}
results = append(results, target.GOOS)
}
return carapace.ActionValues(results...).Tag("operating systems")
return carapace.ActionValues(compilerOSValues(compiler)...).Tag("operating systems")
})
}
+1 -1
View File
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
config.Format,
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf(config.HTTPC2ConfigName),
fmt.Sprintf("%s", config.HTTPC2ConfigName),
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
fmt.Sprintf("%v", builds.Staged[sliverName]),
})
+36 -3
View File
@@ -20,8 +20,10 @@ package generate
import (
"os"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
@@ -44,9 +46,40 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
}
profile := GetImplantProfileByName(name, con)
if profile != nil {
// If SGN is explicitly disabled, make sure this compilation reflects that despite whatever is set in the profile
if SGNDisabled, _ := cmd.Flags().GetBool("disable-sgn"); SGNDisabled {
profile.Config.SGNEnabled = !SGNDisabled
// Override shellcode encoder if explicitly requested.
if cmd.Flags().Changed("shellcode-encoder") {
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
rawEncoder = strings.TrimSpace(rawEncoder)
if rawEncoder == "" {
con.PrintErrorf("shellcode-encoder cannot be empty; use 'none' to disable encoding\n")
return
}
normalized := normalizeShellcodeEncoderName(rawEncoder)
if normalized == "none" {
profile.Config.ShellcodeEncoder = clientpb.ShellcodeEncoder_NONE
profile.Config.SGNEnabled = false
} else if profile.Config.Format != clientpb.OutputFormat_SHELLCODE {
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
} else {
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
con.PrintErrorf("Failed to fetch shellcode encoders: %s\n", err)
return
}
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, rawEncoder)
if !ok {
compatible := compatibleShellcodeEncoderNames(encoderMap, profile.Config.GOARCH)
if len(compatible) == 0 {
con.PrintErrorf("No shellcode encoders are available for arch %s\n", normalizeShellcodeArch(profile.Config.GOARCH))
} else {
con.PrintErrorf("Unsupported shellcode encoder %q for arch %s (valid: %s)\n", rawEncoder, normalizeShellcodeArch(profile.Config.GOARCH), strings.Join(compatible, ", "))
}
return
}
profile.Config.ShellcodeEncoder = encoder
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
}
}
_, err := compile(implantName, profile.Config, save, con)
if err != nil {
+17 -6
View File
@@ -165,10 +165,21 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
properties["obsymbols"] = "disabled"
}
if config.SGNEnabled {
properties["sgn"] = "enabled"
} else {
properties["sgn"] = "disabled"
properties["shellcodeencoder"] = "n/a"
if config.Format == clientpb.OutputFormat_SHELLCODE {
encoder := config.ShellcodeEncoder
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
}
properties["shellcodeencoder"] = map[clientpb.ShellcodeEncoder]string{
clientpb.ShellcodeEncoder_NONE: "none",
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
clientpb.ShellcodeEncoder_XOR: "xor",
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
}[encoder]
if properties["shellcodeencoder"] == "" {
properties["shellcodeencoder"] = fmt.Sprintf("unknown (%d)", int32(encoder))
}
}
reconnect := int(config.ReconnectInterval / int64(math.Pow10(9)))
@@ -311,8 +322,8 @@ func PrintProfileInfo(name string, con *console.SliverClient) {
properties["obsymbols"],
})
tw.AppendRow(table.Row{
"Shikata Ga Nai (SGN) is",
properties["sgn"],
"Shellcode encoder is",
properties["shellcodeencoder"],
})
con.PrintInfof("Obfuscation\n")
+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
}
+241 -82
View File
@@ -23,12 +23,12 @@ package help
*/
import (
"bytes"
"fmt"
"strings"
"text/template"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/theme"
"github.com/charmbracelet/lipgloss"
)
const (
@@ -47,37 +47,45 @@ var (
consts.GenerateStr: generateHelp,
consts.StageListenerStr: stageListenerHelp,
consts.MsfStr: msfHelp,
consts.MsfInjectStr: msfInjectHelp,
consts.PsStr: psHelp,
consts.PingStr: pingHelp,
consts.KillStr: killHelp,
consts.LsStr: lsHelp,
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.CatStr: catHelp,
consts.DownloadStr: downloadHelp,
consts.GrepStr: grepHelp,
consts.HeadStr: headHelp,
consts.TailStr: tailHelp,
consts.UploadStr: uploadHelp,
consts.MkdirStr: mkdirHelp,
consts.RmStr: rmHelp,
consts.ProcdumpStr: procdumpHelp,
consts.ElevateStr: elevateHelp,
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.AliasesStr: loadAliasHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.MountStr: mountHelp,
consts.MsfStr: msfHelp,
consts.MsfInjectStr: msfInjectHelp,
consts.PsStr: psHelp,
consts.PingStr: pingHelp,
consts.KillStr: killHelp,
consts.LsStr: lsHelp,
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.CatStr: catHelp,
consts.EditStr: editHelp,
consts.HexEditStr: hexEditHelp,
consts.DownloadStr: downloadHelp,
consts.GrepStr: grepHelp,
consts.HeadStr: headHelp,
consts.TailStr: tailHelp,
consts.UploadStr: uploadHelp,
consts.MkdirStr: mkdirHelp,
consts.RmStr: rmHelp,
consts.ChtimesStr: chtimesHelp,
consts.ProcdumpStr: procdumpHelp,
consts.ElevateStr: elevateHelp,
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteStr: executeHelp,
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.AliasesStr: loadAliasHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.MountStr: mountHelp,
consts.ShikataGaNai: shikataGaNaiHelp,
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
@@ -124,6 +132,28 @@ var (
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
}
executeHelp = `[[.Bold]]Command:[[.Normal]] execute <path to executable> [options] -- [args...]
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
arguments are be separated from the sliver 'execute' arguments with '--'
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
[[.Bold]]Example:[[.Normal]]
execute /bin/bash --env FOO=1 -- -c env
[*] Execute: /bin/bash [-c env]
[*] Output:
FOO=1
PWD=/Users/moloch/git/sliver
SHLVL=1
_=/usr/bin/env
`
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
`
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
[[.Bold]]About:[[.Normal]] Manage jobs/listeners.`
@@ -159,8 +189,8 @@ You can also stack the C2 configuration with multiple protocols:
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
Supported output formats are Windows PE, Windows DLL, Windows Shellcode, Mach-O, and ELF. The output format is controlled
with the --os and --format flags.
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
Linux ELF, and Linux shellcode (amd64/arm64). The output format is controlled with the --os and --format flags.
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
generate --mtls foo.example.com
@@ -176,6 +206,21 @@ To output a Linux ELF executable file, the following command would be used:
generate --os linux --mtls foo.example.com
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
When generating shellcode (--format shellcode), you can tune the shellcode generator:
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
--shellcode-compress # Enable aPLib compression (windows, macOS, and Linux)
--shellcode-entropy 1|2|3 # (windows only) Entropy: 1=none (default), 2=random names, 3=random+encrypt
--shellcode-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
--shellcode-headers 1|2 # (windows only) PE headers: 1=overwrite (default), 2=keep
--shellcode-thread # (windows only) Unmanaged EXE: run entrypoint as a new thread
--shellcode-unicode # (windows only) Unmanaged DLL: pass Unicode command line
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
Note: macOS and Linux shellcode currently only support --shellcode-compress; other options are Windows-only.
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
DNS canaries are unique per-binary domains that are deliberately NOT obfuscated during the compilation process.
This is done so that these unique domains show up if someone runs 'strings' on the binary, if they then attempt
@@ -281,9 +326,39 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
rmHelp = `[[.Bold]]Command:[[.Normal]] rm [remote path]
[[.Bold]]About:[[.Normal]] Delete a remote file or directory.`
chtimesHelp = `[[.Bold]]Command:[[.Normal]] chtimes [--unix | --unix-ms | --rfc3339 | --rfc1123] <path> <atime> <mtime>
[[.Bold]]About:[[.Normal]] Change access and modification times on a file (timestomp).
[[.Bold]][[.Underline]]Time Formats[[.Normal]]
Default (datetime): "2006-01-02 15:04:05"
--unix: 1704067200
--unix-ms: 1704067200000
--rfc3339: 2024-01-01T00:00:00Z
--rfc1123: Mon, 02 Jan 2006 15:04:05 MST
[[.Bold]][[.Underline]]Examples[[.Normal]]
chtimes /tmp/file "2024-01-01 12:34:56" "2024-01-01 12:35:56"
chtimes --unix /tmp/file 1704112496 1704112556
chtimes --unix-ms /tmp/file 1704112496000 1704112556000
chtimes --rfc3339 /tmp/file 2024-01-01T12:34:56Z 2024-01-01T12:35:56Z
chtimes --rfc1123 /tmp/file "Mon, 02 Jan 2006 15:04:05 MST" "Mon, 02 Jan 2006 16:04:05 MST"
`
catHelp = `[[.Bold]]Command:[[.Normal]] cat <remote path>
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
editHelp = `[[.Bold]]Command:[[.Normal]] edit <remote path>
[[.Bold]]About:[[.Normal]] Download a remote text file, edit it in a built-in TUI editor, and optionally upload changes.
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l) and insert mode (i). Use :wq to save+quit or :q to quit.
[[.Bold]]Syntax:[[.Normal]] Use --syntax <lexer> to force a lexer or --syntax-select to pick one interactively.
[[.Bold]]Line Numbers:[[.Normal]] Use --line-numbers to start with line numbers on; toggle with :n.`
hexEditHelp = `[[.Bold]]Command:[[.Normal]] hex-edit [--max-size <size>] [--offset <byte offset>] <remote path>
[[.Bold]]About:[[.Normal]] Download a remote file and edit it in a built-in hex editor, then optionally upload changes.
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l). Use i for hex edit mode, a for ASCII edit mode, :wq to save+quit or :q to quit.
[[.Bold]]Size Limit:[[.Normal]] Default max size is 8MB. Use --max-size to override (e.g., 512KB, 12MB).
[[.Bold]]Offset:[[.Normal]] Use --offset to jump to a specific byte offset when opening the file.`
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
[[.Bold]][[.Underline]]Filters[[.Normal]]
@@ -368,6 +443,42 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
generate --format shellcode
`
shikataGaNaiHelp = `[[.Bold]]Command:[[.Normal]] shikata-ga-nai [local path to raw shellcode]
[[.Bold]]About:[[.Normal]] Encodes shellcode with Shikata Ga Nai. This uses the same backend implementation as
[[.Bold]]shellcode-encoders encode[[.Normal]] with the shikata_ga_nai encoder, but exposes SGN-specific options
like architecture, iterations, and bad characters.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
Encode x64 shellcode with 3 iterations:
shikata-ga-nai -a amd64 -i 3 /tmp/payload.bin
Avoid bad chars and write to a file:
shikata-ga-nai -b 000a0d -s /tmp/payload.sgn /tmp/payload.bin
[[.Bold]]Related:[[.Normal]] shellcode-encoders encode --encoder shikata_ga_nai ...`
shellcodeEncodersHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders
[[.Bold]]About:[[.Normal]] Lists the shellcode encoders supported by the server along with architectures and descriptions.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
List encoders and supported architectures:
shellcode-encoders
`
shellcodeEncodersEncodeHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders encode [local path to raw shellcode]...
[[.Bold]]About:[[.Normal]] Encodes one or more shellcode files using a server-side encoder. Encoder support depends on
architecture; use [[.Bold]]shellcode-encoders[[.Normal]] to list valid combinations.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
Encode a single file with shikata_ga_nai:
shellcode-encoders encode --encoder shikata_ga_nai --arch amd64 /tmp/payload.bin
Encode multiple files and write outputs to a directory:
shellcode-encoders encode --encoder xor --arch amd64 -o /tmp/out payload1.bin payload2.bin
Avoid bad characters with SGN:
shellcode-encoders encode --encoder shikata_ga_nai --arch 386 -b 000a0d -o /tmp/encoded.bin /tmp/payload.bin
`
migrateHelp = `[[.Bold]]Command:[[.Normal]] migrate <flags>
[[.Bold]]About:[[.Normal]] (Windows Only) Migrates into the process designated by <flags>.`
@@ -1307,24 +1418,6 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
To get information about services, you need to be an authenticated user on the system or domain. To control services, you need administrator or higher privileges.`
)
const (
// ANSI Colors
normal = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
orange = "\033[33m"
blue = "\033[34m"
purple = "\033[35m"
cyan = "\033[36m"
gray = "\033[37m"
bold = "\033[1m"
clearln = "\r\x1b[2K"
upN = "\033[%dA"
downN = "\033[%dB"
underline = "\033[4m"
)
// GetHelpFor - Get help string for a command
func GetHelpFor(cmdName []string) string {
if 0 < len(cmdName) {
@@ -1335,34 +1428,100 @@ func GetHelpFor(cmdName []string) string {
return ""
}
// FormatHelpTmpl - Applies format template to help string
type helpStyleState struct {
bold bool
underline bool
fgSet bool
fg lipgloss.Color
}
func (s *helpStyleState) reset() {
s.bold = false
s.underline = false
s.fgSet = false
s.fg = ""
}
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()
}
+37
View File
@@ -0,0 +1,37 @@
package hexedit
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the hex-edit command.
func Commands(con *console.SliverClient) []*cobra.Command {
hexEditCmd := &cobra.Command{
Use: consts.HexEditStr + " <remote path>",
Short: "Hex edit a remote file",
Long: help.GetHelpFor([]string{consts.HexEditStr}),
Args: cobra.ExactArgs(1),
GroupID: consts.FilesystemHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
HexEditCmd(cmd, con, args)
},
}
flags.Bind("", false, hexEditCmd, func(f *pflag.FlagSet) {
f.String("max-size", "8MB", "maximum file size to load (e.g. 8MB, 512KB)")
f.Int64("offset", 0, "byte offset to jump to (decimal or 0x...)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(hexEditCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to the remote file to hex edit"),
)
return []*cobra.Command{hexEditCmd}
}
+722
View File
@@ -0,0 +1,722 @@
package hexedit
import (
"fmt"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"golang.org/x/term"
)
type editorMode int
const (
modeNormal editorMode = iota
modeHexEdit
modeASCIIEdit
modeCommand
)
type exitAction int
const (
actionNone exitAction = iota
actionQuit
actionSaveQuit
)
const bytesPerLine = 16
type editorResult struct {
Data []byte
Modified bool
Action exitAction
Force bool
}
type editorModel struct {
data []byte
original []byte
modified []bool
modifiedCount int
cursor int
top int
width int
height int
mode editorMode
command string
commandPrefix rune
pending rune
dirty bool
forceQuit bool
action exitAction
filename string
message string
clearMessage bool
nibbleSet bool
nibbleValue byte
}
var (
cursorStyle = lipgloss.NewStyle().Reverse(true)
statusStyle = lipgloss.NewStyle().Reverse(true)
lineStyle = lipgloss.NewStyle()
)
func modifiedStyle() lipgloss.Style {
// Read from the global console theme so theme.yaml changes are reflected.
return console.StyleWarning
}
func modifiedCursorStyle() lipgloss.Style {
// Read from the global console theme so theme.yaml changes are reflected.
return console.StyleWarning.Copy().Reverse(true)
}
func newEditorModel(data []byte, filename string, offset int) *editorModel {
copyData := append([]byte(nil), data...)
model := &editorModel{
data: copyData,
original: append([]byte(nil), data...),
modified: make([]bool, len(copyData)),
mode: modeNormal,
filename: filename,
commandPrefix: ':',
}
if offset > 0 && offset < len(copyData) {
model.cursor = offset
}
return model
}
func (m *editorModel) Init() tea.Cmd {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
m.height = h
} else {
m.width = 80
m.height = 24
}
m.ensureCursorVisible()
}
return nil
}
func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.ensureCursorVisible()
return m, nil
case tea.KeyMsg:
if m.clearMessage {
m.message = ""
m.clearMessage = false
}
if msg.Type == tea.KeyCtrlC {
m.action = actionQuit
return m, tea.Quit
}
switch m.mode {
case modeCommand:
return m.handleCommand(msg)
case modeHexEdit:
return m.handleHexEdit(msg)
case modeASCIIEdit:
return m.handleASCIIEdit(msg)
default:
return m.handleNormal(msg)
}
}
return m, nil
}
func (m *editorModel) View() string {
if m.width == 0 || m.height == 0 {
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
m.width = w
m.height = h
} else {
m.width = 80
m.height = 24
}
m.ensureCursorVisible()
}
textHeight := m.textHeight()
var b strings.Builder
for i := 0; i < textHeight; i++ {
lineIndex := m.top + i
b.WriteString(m.renderLine(lineIndex))
if i < textHeight-1 {
b.WriteByte('\n')
}
}
if m.height < 2 {
return b.String()
}
b.WriteByte('\n')
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
if m.height < 3 {
return b.String()
}
b.WriteByte('\n')
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
return b.String()
}
func (m *editorModel) result() editorResult {
data := append([]byte(nil), m.data...)
return editorResult{
Data: data,
Modified: m.dirty,
Action: m.action,
Force: m.forceQuit,
}
}
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
m.command = ""
m.commandPrefix = ':'
case tea.KeyEnter:
m.executeCommand(strings.TrimSpace(m.command))
m.command = ""
m.mode = modeNormal
m.commandPrefix = ':'
if m.action != actionNone {
return m, tea.Quit
}
case tea.KeyBackspace:
m.command = popRune(m.command)
case tea.KeyRunes:
m.command += string(msg.Runes)
}
return m, nil
}
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
m.nibbleSet = false
case tea.KeyBackspace:
if m.nibbleSet {
m.nibbleSet = false
return m, nil
}
m.moveLeft()
case tea.KeyLeft:
m.moveLeft()
m.nibbleSet = false
case tea.KeyRight:
m.moveRight()
m.nibbleSet = false
case tea.KeyUp:
m.moveUp()
m.nibbleSet = false
case tea.KeyDown:
m.moveDown()
m.nibbleSet = false
case tea.KeyRunes:
for _, r := range msg.Runes {
val, ok := hexNibble(r)
if !ok {
continue
}
if !m.nibbleSet {
m.nibbleValue = val << 4
m.nibbleSet = true
continue
}
m.setByte(m.nibbleValue | val)
m.nibbleSet = false
m.moveRight()
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.mode = modeNormal
case tea.KeyLeft:
m.moveLeft()
case tea.KeyRight:
m.moveRight()
case tea.KeyUp:
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
if r < 0 || r > 0xFF {
continue
}
m.setByte(byte(r))
m.moveRight()
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.Type {
case tea.KeyEsc:
m.pending = 0
case tea.KeyLeft:
m.moveLeft()
case tea.KeyRight:
m.moveRight()
case tea.KeyUp:
m.moveUp()
case tea.KeyDown:
m.moveDown()
case tea.KeyRunes:
for _, r := range msg.Runes {
if m.handleNormalRune(r) {
break
}
}
}
m.ensureCursorVisible()
return m, nil
}
func (m *editorModel) handleNormalRune(r rune) bool {
if m.pending != 0 {
handled := true
switch m.pending {
case 'g':
if r == 'g' {
m.gotoTop()
} else {
handled = false
}
default:
handled = false
}
m.pending = 0
if handled {
return true
}
}
switch r {
case 'h':
m.moveLeft()
case 'j':
m.moveDown()
case 'k':
m.moveUp()
case 'l':
m.moveRight()
case '0':
m.moveLineStart()
case '$':
m.moveLineEnd()
case 'i':
m.mode = modeHexEdit
m.nibbleSet = false
case 'a':
m.mode = modeASCIIEdit
case 'x':
m.setByte(0x00)
case 'g':
m.pending = 'g'
case 'G':
m.gotoBottom()
case ':':
m.mode = modeCommand
m.command = ""
m.commandPrefix = ':'
}
return false
}
func (m *editorModel) executeCommand(cmd string) {
if cmd == "" {
return
}
if isNumericOffset(cmd) {
if value, err := strconv.ParseInt(cmd, 0, 64); err == nil {
m.gotoOffset(int(value))
return
}
}
switch strings.ToLower(cmd) {
case "q":
m.action = actionQuit
case "q!":
m.action = actionQuit
m.forceQuit = true
case "wq", "x":
m.action = actionSaveQuit
case "w":
m.message = "Use :wq to upload and exit"
m.clearMessage = true
default:
m.message = fmt.Sprintf("Unknown command: %s", cmd)
m.clearMessage = true
}
}
func (m *editorModel) renderLine(lineIndex int) string {
totalWidth := m.textWidth()
if totalWidth <= 0 {
return ""
}
if lineIndex >= m.totalLines() {
return lineStyle.Width(totalWidth).Render("~")
}
lineStart := lineIndex * bytesPerLine
hexCells := make([]string, bytesPerLine)
asciiCells := make([]string, bytesPerLine)
for i := 0; i < bytesPerLine; i++ {
idx := lineStart + i
if idx >= len(m.data) {
hexCells[i] = " "
asciiCells[i] = " "
continue
}
byteVal := m.data[idx]
hexRaw := fmt.Sprintf("%02x", byteVal)
if idx == m.cursor && m.mode == modeHexEdit && m.nibbleSet {
hexRaw = string([]byte{hexChar(m.nibbleValue >> 4), '_'})
}
asciiRaw := string(printableByte(byteVal))
isModified := m.isModified(idx)
if idx == m.cursor {
if isModified {
hexCells[i] = modifiedCursorStyle().Render(hexRaw)
asciiCells[i] = modifiedCursorStyle().Render(asciiRaw)
} else {
hexCells[i] = cursorStyle.Render(hexRaw)
asciiCells[i] = cursorStyle.Render(asciiRaw)
}
continue
}
if isModified {
hexCells[i] = modifiedStyle().Render(hexRaw)
asciiCells[i] = modifiedStyle().Render(asciiRaw)
continue
}
hexCells[i] = hexRaw
asciiCells[i] = asciiRaw
}
var b strings.Builder
b.WriteString(fmt.Sprintf("%08x", lineStart))
b.WriteString(": ")
for i := 0; i < bytesPerLine; i++ {
if i == 8 {
b.WriteByte(' ')
}
b.WriteString(hexCells[i])
if i < bytesPerLine-1 {
b.WriteByte(' ')
}
}
b.WriteString(" |")
for i := 0; i < bytesPerLine; i++ {
b.WriteString(asciiCells[i])
}
b.WriteString("|")
return lineStyle.Width(totalWidth).Render(b.String())
}
func (m *editorModel) statusLine() string {
mode := "NORMAL"
switch m.mode {
case modeHexEdit:
mode = "HEX"
case modeASCIIEdit:
mode = "ASCII"
case modeCommand:
mode = "COMMAND"
}
fileName := baseName(m.filename)
if fileName == "" {
fileName = "[No Name]"
}
modified := ""
if m.dirty {
modified = " [+]"
}
cursor := m.cursor
return fmt.Sprintf(" %s %s Off 0x%08x (%d) Size %s%s", mode, fileName, cursor, cursor, util.ByteCountBinary(int64(len(m.data))), modified)
}
func (m *editorModel) commandLine() string {
if m.mode == modeCommand {
return ":" + m.command
}
if m.message != "" {
return m.message
}
switch m.mode {
case modeHexEdit:
return "ESC: normal hex: 0-9/a-f :wq save+quit :q quit"
case modeASCIIEdit:
return "ESC: normal type to edit bytes :wq save+quit :q quit"
default:
return "i: hex edit a: ascii edit h/j/k/l move x zero :wq save+quit :q quit"
}
}
func (m *editorModel) textHeight() int {
if m.height <= 2 {
return 1
}
return m.height - 2
}
func (m *editorModel) textWidth() int {
if m.width <= 0 {
return 1
}
return m.width
}
func (m *editorModel) totalLines() int {
if len(m.data) == 0 {
return 1
}
return (len(m.data) + bytesPerLine - 1) / bytesPerLine
}
func (m *editorModel) ensureCursorVisible() {
if len(m.data) == 0 {
m.cursor = 0
m.top = 0
return
}
if m.cursor < 0 {
m.cursor = 0
}
if m.cursor >= len(m.data) {
m.cursor = len(m.data) - 1
}
line := m.cursor / bytesPerLine
height := m.textHeight()
if line < m.top {
m.top = line
} else if line >= m.top+height {
m.top = line - height + 1
}
if m.top < 0 {
m.top = 0
}
maxTop := m.totalLines() - height
if maxTop < 0 {
maxTop = 0
}
if m.top > maxTop {
m.top = maxTop
}
}
func (m *editorModel) moveLeft() {
if len(m.data) == 0 {
return
}
if m.cursor > 0 {
m.cursor--
}
}
func (m *editorModel) moveRight() {
if len(m.data) == 0 {
return
}
if m.cursor < len(m.data)-1 {
m.cursor++
}
}
func (m *editorModel) moveUp() {
if len(m.data) == 0 {
return
}
if m.cursor >= bytesPerLine {
m.cursor -= bytesPerLine
}
}
func (m *editorModel) moveDown() {
if len(m.data) == 0 {
return
}
if m.cursor+bytesPerLine < len(m.data) {
m.cursor += bytesPerLine
}
}
func (m *editorModel) moveLineStart() {
if len(m.data) == 0 {
return
}
line := m.cursor / bytesPerLine
m.cursor = line * bytesPerLine
}
func (m *editorModel) moveLineEnd() {
if len(m.data) == 0 {
return
}
line := m.cursor / bytesPerLine
end := (line+1)*bytesPerLine - 1
if end >= len(m.data) {
end = len(m.data) - 1
}
m.cursor = end
}
func (m *editorModel) gotoTop() {
if len(m.data) == 0 {
m.cursor = 0
return
}
m.cursor = 0
}
func (m *editorModel) gotoBottom() {
if len(m.data) == 0 {
m.cursor = 0
return
}
m.cursor = len(m.data) - 1
}
func (m *editorModel) gotoOffset(offset int) {
if len(m.data) == 0 {
m.message = "Empty file"
m.clearMessage = true
return
}
if offset < 0 || offset >= len(m.data) {
m.message = fmt.Sprintf("Offset out of range: %d", offset)
m.clearMessage = true
return
}
m.cursor = offset
m.ensureCursorVisible()
}
func (m *editorModel) setByte(value byte) {
if len(m.data) == 0 {
m.message = "Empty file"
m.clearMessage = true
return
}
if m.cursor >= len(m.data) || m.cursor >= len(m.original) {
return
}
wasModified := m.modified[m.cursor]
orig := m.original[m.cursor]
m.data[m.cursor] = value
if value == orig {
if wasModified {
m.modified[m.cursor] = false
if m.modifiedCount > 0 {
m.modifiedCount--
}
}
} else if !wasModified {
m.modified[m.cursor] = true
m.modifiedCount++
}
m.dirty = m.modifiedCount > 0
}
func printableByte(b byte) byte {
if b >= 32 && b <= 126 {
return b
}
return '.'
}
func (m *editorModel) isModified(idx int) bool {
if idx < 0 || idx >= len(m.modified) {
return false
}
return m.modified[idx]
}
func hexChar(n byte) byte {
if n < 10 {
return '0' + n
}
return 'a' + (n - 10)
}
func hexNibble(r rune) (byte, bool) {
switch {
case r >= '0' && r <= '9':
return byte(r - '0'), true
case r >= 'a' && r <= 'f':
return byte(r-'a') + 10, true
case r >= 'A' && r <= 'F':
return byte(r-'A') + 10, true
}
return 0, false
}
func isNumericOffset(value string) bool {
value = strings.TrimSpace(value)
if value == "" {
return false
}
if strings.HasPrefix(strings.ToLower(value), "0x") {
_, err := strconv.ParseInt(value, 0, 64)
return err == nil
}
for _, r := range value {
if r < '0' || r > '9' {
return false
}
}
return true
}
func popRune(value string) string {
runes := []rune(value)
if len(runes) == 0 {
return value
}
return string(runes[:len(runes)-1])
}
func baseName(path string) string {
if path == "" {
return ""
}
clean := strings.ReplaceAll(path, "\\", "/")
if idx := strings.LastIndex(clean, "/"); idx != -1 {
return clean[idx+1:]
}
return path
}
+349
View File
@@ -0,0 +1,349 @@
package hexedit
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/encoders"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"google.golang.org/protobuf/proto"
)
const (
beaconPollInterval = 1 * time.Second
)
// HexEditCmd downloads a remote file, opens it in a hex editor, and optionally uploads changes.
func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
remotePath := strings.TrimSpace(args[0])
if remotePath == "" {
con.PrintErrorf("Missing parameter: remote path\n")
return
}
maxSizeStr, _ := cmd.Flags().GetString("max-size")
maxSize, err := parseByteSize(maxSizeStr)
if err != nil {
con.PrintErrorf("Invalid --max-size %q: %s\n", maxSizeStr, err)
return
}
if maxSize <= 0 {
con.PrintErrorf("--max-size must be greater than zero\n")
return
}
offset, _ := cmd.Flags().GetInt64("offset")
if offset < 0 {
con.PrintErrorf("--offset must be zero or positive\n")
return
}
fetchSize := maxSize + 1
if fetchSize < maxSize {
fetchSize = maxSize
}
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
download, err := downloadRemote(ctx, con, cmd, remotePath, fetchSize)
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
if !download.Exists {
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
return
}
if download.IsDir {
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
return
}
data := download.Data
if download.Encoder == "gzip" {
data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s\n", err)
return
}
}
if int64(len(data)) > maxSize {
con.PrintErrorf("Remote file size %s exceeds --max-size %s\n", util.ByteCountBinary(int64(len(data))), util.ByteCountBinary(maxSize))
return
}
if offset > 0 {
if len(data) == 0 {
con.PrintErrorf("Offset %d out of range for empty file\n", offset)
return
}
if offset >= int64(len(data)) {
con.PrintErrorf("Offset %d out of range (file size %d bytes)\n", offset, len(data))
return
}
}
path := download.Path
if path == "" {
path = remotePath
}
for {
result, err := runHexEditor(data, path, int(offset))
if err != nil {
con.PrintErrorf("Editor error: %s\n", err)
return
}
data = result.Data
if result.Action == actionNone {
return
}
if !result.Modified {
if result.Action == actionSaveQuit {
con.PrintInfof("No changes to upload for %s\n", path)
}
return
}
switch result.Action {
case actionSaveQuit:
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if !uploadNow {
keepEditing, err := confirm("Continue editing?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if keepEditing {
continue
}
return
}
if err := uploadEditedContent(cmd, con, path, data); err != nil {
con.PrintErrorf("%s\n", err)
}
return
case actionQuit:
if result.Force {
return
}
discard, err := confirm("Discard changes?")
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if discard {
return
}
}
}
}
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
model := newEditorModel(data, filename, offset)
program := tea.NewProgram(model, tea.WithAltScreen())
finalModel, err := program.Run()
if err != nil {
return editorResult{}, err
}
editor, ok := finalModel.(*editorModel)
if !ok {
return editorResult{}, fmt.Errorf("unexpected editor state")
}
return editor.result(), nil
}
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string, maxBytes int64) (*sliverpb.Download, error) {
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
RestrictedToFile: true,
MaxBytes: maxBytes,
})
if err != nil {
return nil, err
}
if download.Response != nil && download.Response.Async {
taskID := download.Response.TaskID
download = &sliverpb.Download{}
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
return nil, err
}
}
return download, nil
}
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
ctx, cancel := con.GrpcContext(cmd)
defer cancel()
uploadData, err := new(encoders.Gzip).Encode(data)
if err != nil {
return err
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
Request: con.ActiveTarget.Request(cmd),
Path: path,
Data: uploadData,
Encoder: "gzip",
IsDirectory: false,
Overwrite: true,
})
if err == nil && upload.Response != nil && upload.Response.Async {
taskID := upload.Response.TaskID
upload = &sliverpb.Upload{}
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
}
ctrl <- true
<-ctrl
if err != nil {
return err
}
filesystem.PrintUpload(upload, con)
return nil
}
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
task, err := waitForBeaconTask(ctx, con, taskID)
if err != nil {
return err
}
state := strings.ToLower(task.State)
if state != "completed" {
if state == "" {
state = "unknown"
}
return fmt.Errorf("beacon task %s %s", task.ID, state)
}
if len(task.Response) == 0 {
return fmt.Errorf("beacon task %s returned empty response", task.ID)
}
return proto.Unmarshal(task.Response, out)
}
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
ticker := time.NewTicker(beaconPollInterval)
defer ticker.Stop()
for {
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
if err != nil {
return nil, err
}
state := strings.ToLower(task.State)
if state == "completed" || state == "failed" || state == "canceled" {
return task, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
}
}
}
func confirm(title string) (bool, error) {
answer := false
if err := forms.Confirm(title, &answer); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return false, nil
}
return false, err
}
return answer, nil
}
func parseByteSize(input string) (int64, error) {
value := strings.TrimSpace(input)
if value == "" {
return 0, fmt.Errorf("size is required")
}
value = strings.ReplaceAll(value, " ", "")
value = strings.ToUpper(value)
idx := 0
for idx < len(value) {
c := value[idx]
if (c >= '0' && c <= '9') || c == '.' {
idx++
continue
}
break
}
if idx == 0 {
return 0, fmt.Errorf("invalid size %q", input)
}
numStr := value[:idx]
unitStr := strings.TrimSpace(value[idx:])
if unitStr == "" {
unitStr = "B"
}
multiplier := int64(1)
switch unitStr {
case "B":
multiplier = 1
case "K", "KB", "KIB":
multiplier = 1024
case "M", "MB", "MIB":
multiplier = 1024 * 1024
case "G", "GB", "GIB":
multiplier = 1024 * 1024 * 1024
case "T", "TB", "TIB":
multiplier = 1024 * 1024 * 1024 * 1024
default:
return 0, fmt.Errorf("unknown size unit %q", unitStr)
}
numVal, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return 0, fmt.Errorf("invalid size %q", input)
}
if numVal <= 0 {
return 0, fmt.Errorf("size must be greater than zero")
}
size := int64(numVal * float64(multiplier))
if size <= 0 {
return 0, fmt.Errorf("size is out of range")
}
return size, nil
}
+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)
-1
View File
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
f.StringP("lhost", "L", "", "interface to bind server to")
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
f.BoolP("disable-otp", "D", false, "disable otp authentication")
})
// HTTP
+4 -6
View File
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
lhost, _ := cmd.Flags().GetString("lhost")
lport, _ := cmd.Flags().GetUint32("lport")
canaries, _ := cmd.Flags().GetBool("no-canaries")
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
Domains: domains,
Host: lhost,
Port: lport,
Canaries: !canaries,
EnforceOTP: !enforceOTP,
Domains: domains,
Host: lhost,
Port: lport,
Canaries: !canaries,
})
con.Println()
if err != nil {
+2 -1
View File
@@ -32,12 +32,13 @@ func WGListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
nport, _ := cmd.Flags().GetUint32("nport")
keyExchangePort, _ := cmd.Flags().GetUint32("key-port")
con.PrintInfof("Starting Wireguard listener ...\n")
con.PrintInfof("Starting Wireguard listener (udp:%d, tcp-comms:%d, key-exchange:%d) ...\n", lport, nport, keyExchangePort)
wg, err := con.Rpc.StartWGListener(context.Background(), &clientpb.WGListenerReq{
Port: lport,
NPort: nport,
KeyPort: keyExchangePort,
})
con.Println()
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
+1 -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) {
+7
View File
@@ -1,6 +1,7 @@
package mcp
import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
slivermcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
@@ -26,6 +27,12 @@ func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
ServerVersion: version,
}.WithDefaults()
msg := `Do you know what prompt injection is and are you an adult?`
if !settings.IsUserAnAdultWithPrompt(con, msg) {
con.PrintErrorf("Failed to start MCP server, the user is not qualified to use feature\n")
return
}
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
con.PrintErrorf("%s\n", err)
return
+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 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",
+6 -2
View File
@@ -150,11 +150,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", false, chtimesCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
f.Bool("unix", false, "interpret timestamps as Unix seconds")
f.Bool("unix-ms", false, "interpret timestamps as Unix milliseconds")
f.Bool("rfc3339", false, "interpret timestamps as RFC3339")
f.Bool("rfc1123", false, "interpret timestamps as RFC1123")
})
carapace.Gen(chtimesCmd).PositionalCompletion(
carapace.ActionValues().Usage("path to file to change access timestamps"),
carapace.ActionValues().Usage("last accessed time in DateTime format, i.e. 2006-01-02 15:04:05"),
carapace.ActionValues().Usage("last modified time in DateTime format, i.e. 2006-01-02 15:04:05"),
carapace.ActionValues().Usage("last accessed time (default: 2006-01-02 15:04:05; see --help for format flags)"),
carapace.ActionValues().Usage("last modified time (default: 2006-01-02 15:04:05; see --help for format flags)"),
)
getprivsCmd := &cobra.Command{
+12
View File
@@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
@@ -44,6 +45,17 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
process, _ := cmd.Flags().GetString("process")
config := con.GetActiveSessionConfig()
/* If the HTTP C2 Config name is not defined, then put in the default value
This will have no effect on implants that do not use HTTP C2
Also this should be overridden when the build info is pulled from the
database, but if there is no build info and we have to create the build
from scratch, we need to have something in here.
*/
if config.HTTPC2ConfigName == "" {
config.HTTPC2ConfigName = consts.DefaultC2Profile
}
ctrl := make(chan bool)
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
+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{
+39
View File
@@ -20,6 +20,7 @@ package reconfig
import (
"context"
"net/url"
"time"
"github.com/bishopfox/sliver/client/console"
@@ -52,6 +53,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 +75,47 @@ 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 {
// must be the same protocol
if newURI.Scheme != currentURI.Scheme {
con.PrintErrorf("Cannot switch protocol from %s to %s (protocol must match compiled implant)\n",
currentURI.Scheme, newURI.Scheme)
return
}
// for now only support the same Host/C2 Server (becuase the implant crypto keys problem)
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())
}
}
}
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 {
+9 -1
View File
@@ -44,8 +44,10 @@ import (
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/operators"
"github.com/bishopfox/sliver/client/command/reaction"
"github.com/bishopfox/sliver/client/command/serverctx"
"github.com/bishopfox/sliver/client/command/sessions"
"github.com/bishopfox/sliver/client/command/settings"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
sgn "github.com/bishopfox/sliver/client/command/shikata-ga-nai"
"github.com/bishopfox/sliver/client/command/socks"
"github.com/bishopfox/sliver/client/command/taskmany"
@@ -69,6 +71,10 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
HiddenDefaultCmd: true,
},
}
if !con.IsCLI {
server.SilenceErrors = true
server.SilenceUsage = true
}
// Utility function to be used for binding new commands to
// the sliver menu: call the function with the name of the
@@ -92,6 +98,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// Core
bind(consts.GenericHelpGroup,
exit.Command,
serverctx.Commands,
licenses.Commands,
settings.Commands,
alias.Commands,
@@ -103,7 +110,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
crack.Commands,
certificates.Commands,
clean.Command,
aka.ServerCommands,
aka.ServerCommands,
)
// C2 Network
@@ -119,6 +126,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// Payloads
bind(consts.PayloadsHelpGroup,
sgn.Commands,
shellcodeencoders.Commands,
generate.Commands,
builders.Commands,
)
+609
View File
@@ -0,0 +1,609 @@
//go:build client
package serverctx
import (
"context"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"sort"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
clienttheme "github.com/bishopfox/sliver/client/theme"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"google.golang.org/grpc/connectivity"
)
// Commands returns client-only server connection context commands.
func Commands(con *console.SliverClient) []*cobra.Command {
root := &cobra.Command{
Use: "server",
Short: "Show and manage the current server connection",
Run: func(cmd *cobra.Command, args []string) {
serverInfo(cmd, con)
},
}
switchCmd := &cobra.Command{
Use: "switch",
Short: "Switch to another server/operator profile",
Run: func(cmd *cobra.Command, args []string) {
serverSwitch(cmd, con)
},
}
root.AddCommand(switchCmd)
return []*cobra.Command{root}
}
func serverInfo(_ *cobra.Command, con *console.SliverClient) {
details, state, ok := con.CurrentConnection()
if !ok || con.Rpc == nil {
con.PrintErrorf("Not connected\n")
return
}
var hostPort string
operator := ""
fingerprint := ""
if details != nil && details.Config != nil {
hostPort = fmt.Sprintf("%s:%d", details.Config.LHost, details.Config.LPort)
operator = operatorFromConfig(details.Config)
fingerprint = shortCertFingerprint(details.Config)
}
if operator == "" {
operator = "<unknown>"
}
if hostPort == "" {
hostPort = "<unknown>"
}
ver, err := con.Rpc.GetVersion(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("GetVersion failed: %s\n", err)
return
}
const keyWidth = 8 // Align on ": " (e.g., "Operator", "Version")
label := func(key string) string {
// Bold "default/white" label keys, padded so colons align.
return console.StyleBoldGray.Render(fmt.Sprintf("%-*s:", keyWidth, key))
}
valueSecondary := func(s string) string { return console.StylePurple.Render(s) }
valueDim := func(s string) string { return console.StyleGray.Render(s) }
stateStyle := console.StyleGray
switch state {
case connectivity.Ready:
stateStyle = console.StyleGreen
case connectivity.Connecting:
stateStyle = console.StyleOrange
case connectivity.TransientFailure, connectivity.Shutdown:
stateStyle = console.StyleRed
case connectivity.Idle:
stateStyle = console.StyleOrange
}
// Server/operator values should be "default" terminal text (not themed colorized).
con.Printf("%s %s\n", label("Server"), hostPort)
if details != nil && details.ConfigKey != "" {
con.Printf("%s %s\n", label("Profile"), valueSecondary(details.ConfigKey))
}
con.Printf("%s %s\n", label("Operator"), operator)
if fingerprint != "" {
con.Printf("%s %s\n", label("Cert"), valueDim(fingerprint))
}
con.Printf("%s %s\n", label("gRPC"), stateStyle.Render(state.String()))
con.Printf("%s %s %s\n",
label("Version"),
console.StyleBold.Render(fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)),
valueDim("("+ver.Commit+")"),
)
}
func serverSwitch(_ *cobra.Command, con *console.SliverClient) {
configs := assets.GetConfigs()
if len(configs) == 0 {
con.PrintErrorf("No config files found at %s\n", assets.GetConfigDir())
return
}
keys := make([]string, 0, len(configs))
for k := range configs {
keys = append(keys, k)
}
sort.Strings(keys)
mgr := getConsoleManager(con)
mgr.syncActiveFromConsole(con)
instances, activeID := mgr.snapshot()
result, err := runSwitchTUI(instances, activeID, keys, mgr.configInstanceCountsLocked(instances))
if err != nil {
con.PrintErrorf("TUI error: %s\n", err)
return
}
if result.Action == actionNone {
return
}
switch result.Action {
case actionSwitchExisting:
inst := mgr.findInstance(result.ExistingID)
if inst == nil || inst.Config == nil {
con.PrintErrorf("Invalid selection\n")
return
}
if inst.ID == activeID {
con.PrintInfof("Already on %s\n", inst.DisplayName())
return
}
rpc, conn, err := transport.MTLSConnect(inst.Config)
if err != nil {
con.PrintErrorf("Connection failed: %s\n", err)
return
}
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: inst.ConfigKey, Config: inst.Config}); err != nil {
_ = conn.Close()
con.PrintErrorf("Switch failed: %s\n", err)
return
}
mgr.setActive(inst.ID)
con.PrintSuccessf("Switched to %s:%d (%s)\n", inst.Config.LHost, inst.Config.LPort, inst.DisplayName())
case actionNewConsole:
cfg := configs[result.ConfigKey]
if cfg == nil {
con.PrintErrorf("Invalid selection\n")
return
}
rpc, conn, err := transport.MTLSConnect(cfg)
if err != nil {
con.PrintErrorf("Connection failed: %s\n", err)
return
}
if err := con.SetConnection(rpc, conn, &console.ConnectionDetails{ConfigKey: result.ConfigKey, Config: cfg}); err != nil {
_ = conn.Close()
con.PrintErrorf("Switch failed: %s\n", err)
return
}
inst := mgr.addInstance(result.ConfigKey, cfg)
con.PrintSuccessf("Connected to %s:%d (%s)\n", cfg.LHost, cfg.LPort, inst.DisplayName())
}
}
var consoleManagers sync.Map // *console.SliverClient -> *consoleManager
type consoleManager struct {
mu sync.Mutex
nextID int
activeID string
insts []*consoleInstance
}
type consoleInstance struct {
ID string
ConfigKey string
Config *assets.ClientConfig
CreatedAt time.Time
LastUsed time.Time
}
func (i *consoleInstance) DisplayName() string {
if i == nil {
return ""
}
return fmt.Sprintf("console-%s", i.ID)
}
func getConsoleManager(con *console.SliverClient) *consoleManager {
if con == nil {
return &consoleManager{}
}
if v, ok := consoleManagers.Load(con); ok {
return v.(*consoleManager)
}
m := &consoleManager{}
actual, _ := consoleManagers.LoadOrStore(con, m)
return actual.(*consoleManager)
}
func (m *consoleManager) syncActiveFromConsole(con *console.SliverClient) {
if m == nil || con == nil {
return
}
details, _, ok := con.CurrentConnection()
if !ok || details == nil || details.Config == nil || details.ConfigKey == "" {
return
}
m.mu.Lock()
defer m.mu.Unlock()
// If we already have an active instance, keep it. Otherwise create one from the current connection.
if m.activeID != "" {
return
}
m.nextID++
id := fmt.Sprintf("%d", m.nextID)
now := time.Now()
m.activeID = id
m.insts = append(m.insts, &consoleInstance{
ID: id,
ConfigKey: details.ConfigKey,
Config: details.Config,
CreatedAt: now,
LastUsed: now,
})
}
func (m *consoleManager) snapshot() ([]*consoleInstance, string) {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]*consoleInstance, 0, len(m.insts))
out = append(out, m.insts...)
return out, m.activeID
}
func (m *consoleManager) findInstance(id string) *consoleInstance {
m.mu.Lock()
defer m.mu.Unlock()
for _, inst := range m.insts {
if inst != nil && inst.ID == id {
return inst
}
}
return nil
}
func (m *consoleManager) setActive(id string) {
m.mu.Lock()
defer m.mu.Unlock()
m.activeID = id
for _, inst := range m.insts {
if inst != nil && inst.ID == id {
inst.LastUsed = time.Now()
break
}
}
}
func (m *consoleManager) addInstance(configKey string, cfg *assets.ClientConfig) *consoleInstance {
m.mu.Lock()
defer m.mu.Unlock()
m.nextID++
id := fmt.Sprintf("%d", m.nextID)
now := time.Now()
inst := &consoleInstance{
ID: id,
ConfigKey: configKey,
Config: cfg,
CreatedAt: now,
LastUsed: now,
}
m.insts = append(m.insts, inst)
m.activeID = id
return inst
}
func (m *consoleManager) configInstanceCountsLocked(insts []*consoleInstance) map[string]int {
// Caller provides insts snapshot; we just count.
counts := map[string]int{}
for _, inst := range insts {
if inst == nil {
continue
}
counts[inst.ConfigKey]++
}
return counts
}
type switchAction int
const (
actionNone switchAction = iota
actionSwitchExisting
actionNewConsole
)
type switchResult struct {
Action switchAction
ExistingID string
ConfigKey string
}
type switchTab int
const (
tabExisting switchTab = iota
tabNew
)
type switchTUIModel struct {
tab switchTab
existing *huh.Form
newConn *huh.Form
result switchResult
width int
height int
theme *huh.Theme
styles switchStyles
activeID string
counts map[string]int
initOnce bool
keyMap *huh.KeyMap
}
type switchStyles struct {
frame lipgloss.Style
header lipgloss.Style
tabActive lipgloss.Style
tabInactive lipgloss.Style
footer lipgloss.Style
footerDim lipgloss.Style
divider lipgloss.Style
}
func runSwitchTUI(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) (switchResult, error) {
m := newSwitchTUIModel(instances, activeID, configKeys, counts)
prog := tea.NewProgram(m, tea.WithAltScreen())
finalModel, err := prog.Run()
if err != nil {
return switchResult{}, err
}
out, ok := finalModel.(switchTUIModel)
if !ok {
return switchResult{}, fmt.Errorf("unexpected TUI state")
}
return out.result, nil
}
func newSwitchTUIModel(instances []*consoleInstance, activeID string, configKeys []string, counts map[string]int) switchTUIModel {
keyMap := huh.NewDefaultKeyMap()
// Free up tab for switching between views; use enter to select/advance.
keyMap.Select.Next = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select"))
keyMap.Select.Submit = key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit"))
huhTheme := clienttheme.HuhTheme()
styles := switchStyles{
frame: lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(clienttheme.DefaultMod(300)).
Padding(0, 1),
header: lipgloss.NewStyle().MarginBottom(1),
tabActive: lipgloss.NewStyle().
Bold(true).
Foreground(clienttheme.DefaultMod(900)).
Background(clienttheme.Primary()).
Padding(0, 1),
tabInactive: lipgloss.NewStyle().
Foreground(clienttheme.DefaultMod(500)).
Background(clienttheme.DefaultMod(50)).
Padding(0, 1),
footer: lipgloss.NewStyle().MarginTop(1),
footerDim: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(400)),
divider: lipgloss.NewStyle().Foreground(clienttheme.DefaultMod(300)),
}
existingID := activeID
existingOptions := make([]huh.Option[string], 0, len(instances))
for _, inst := range instances {
if inst == nil {
continue
}
label := fmt.Sprintf("%s %s", inst.DisplayName(), inst.ConfigKey)
if inst.ID == activeID && activeID != "" {
label = fmt.Sprintf("* %s", label)
}
existingOptions = append(existingOptions, huh.NewOption(label, inst.ID))
}
if len(existingOptions) == 0 {
// Always provide at least one option so Huh doesn't error.
existingOptions = append(existingOptions, huh.NewOption("(no existing consoles)", ""))
}
existingSelect := huh.NewSelect[string]().
Title("Existing Consoles").
Description("Switch to an already-created console context. Use / to filter.").
Options(existingOptions...).
Key("existing").
Value(&existingID)
existingForm := huh.NewForm(huh.NewGroup(existingSelect)).
WithTheme(huhTheme).
WithKeyMap(keyMap).
WithShowHelp(false)
newKey := ""
configOptions := make([]huh.Option[string], 0, len(configKeys))
for _, k := range configKeys {
label := k
if n := counts[k]; n > 0 {
label = fmt.Sprintf("%s (existing: %d)", k, n)
}
configOptions = append(configOptions, huh.NewOption(label, k))
if newKey == "" {
newKey = k
}
}
newSelect := huh.NewSelect[string]().
Title("New Connection / Console").
Description("Create a new console context from a profile in ~/.sliver-client/configs. Use / to filter.").
Options(configOptions...).
Key("config").
Value(&newKey)
newForm := huh.NewForm(huh.NewGroup(newSelect)).
WithTheme(huhTheme).
WithKeyMap(keyMap).
WithShowHelp(false)
return switchTUIModel{
tab: tabExisting,
existing: existingForm,
newConn: newForm,
theme: huhTheme,
styles: styles,
activeID: activeID,
counts: counts,
keyMap: keyMap,
}
}
func (m switchTUIModel) Init() tea.Cmd {
// Initialize both forms so they're ready when switching tabs.
return tea.Batch(m.existing.Init(), m.newConn.Init())
}
func (m switchTUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
// Forward to both forms so they size correctly.
modelA, cmdA := m.existing.Update(msg)
modelB, cmdB := m.newConn.Update(msg)
m.existing = modelA.(*huh.Form)
m.newConn = modelB.(*huh.Form)
return m, tea.Batch(cmdA, cmdB)
case tea.KeyMsg:
switch msg.String() {
case "tab":
if m.tab == tabExisting {
m.tab = tabNew
} else {
m.tab = tabExisting
}
return m, nil
case "shift+tab":
if m.tab == tabExisting {
m.tab = tabNew
} else {
m.tab = tabExisting
}
return m, nil
}
}
// Delegate to active tab's form.
switch m.tab {
case tabExisting:
model, cmd := m.existing.Update(msg)
m.existing = model.(*huh.Form)
if m.existing.State == huh.StateCompleted {
id := m.existing.GetString("existing")
if id != "" {
m.result = switchResult{Action: actionSwitchExisting, ExistingID: id}
}
return m, tea.Quit
}
if m.existing.State == huh.StateAborted {
m.result = switchResult{Action: actionNone}
return m, tea.Quit
}
return m, cmd
case tabNew:
model, cmd := m.newConn.Update(msg)
m.newConn = model.(*huh.Form)
if m.newConn.State == huh.StateCompleted {
key := m.newConn.GetString("config")
if key != "" {
m.result = switchResult{Action: actionNewConsole, ConfigKey: key}
}
return m, tea.Quit
}
if m.newConn.State == huh.StateAborted {
m.result = switchResult{Action: actionNone}
return m, tea.Quit
}
return m, cmd
}
return m, nil
}
func (m switchTUIModel) View() string {
header := m.renderHeader()
body := ""
switch m.tab {
case tabExisting:
body = m.existing.View()
case tabNew:
body = m.newConn.View()
}
footer := m.renderFooter()
content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer)
return m.styles.frame.Render(content)
}
func (m switchTUIModel) renderHeader() string {
left := m.styles.tabInactive.Render("Existing")
right := m.styles.tabInactive.Render("New")
if m.tab == tabExisting {
left = m.styles.tabActive.Render("Existing")
} else {
right = m.styles.tabActive.Render("New")
}
line := lipgloss.JoinHorizontal(lipgloss.Left, left, m.styles.divider.Render(" "), right)
return m.styles.header.Render(line)
}
func (m switchTUIModel) renderFooter() string {
keys := "tab: switch view enter: select /: filter ctrl+c: cancel"
return m.styles.footerDim.Render(keys)
}
func operatorFromConfig(cfg *assets.ClientConfig) string {
if cfg == nil {
return ""
}
// Prefer the certificate CN, since config.Operator is mostly informational.
block, _ := pem.Decode([]byte(cfg.Certificate))
if block != nil {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil && cert.Subject.CommonName != "" {
return cert.Subject.CommonName
}
}
if strings.TrimSpace(cfg.Operator) != "" {
return strings.TrimSpace(cfg.Operator)
}
return ""
}
func shortCertFingerprint(cfg *assets.ClientConfig) string {
if cfg == nil {
return ""
}
digest := sha256.Sum256([]byte(cfg.Certificate))
return fmt.Sprintf("%x", digest[:8])
}
+13
View File
@@ -0,0 +1,13 @@
//go:build !client
package serverctx
import (
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
// Commands is a no-op when building without the `client` build tag (e.g. sliver-server).
func Commands(_ *console.SliverClient) []*cobra.Command {
return nil
}
+68 -15
View File
@@ -43,6 +43,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["interact"] = SessionIDCompleter(con)
(*comp)["kill"] = SessionIDCompleter(con)
})
registerSessionIDFlagCompletion(sessionsCmd, "interact", con)
sessionsPruneCmd := &cobra.Command{
Use: consts.PruneStr,
@@ -63,26 +64,78 @@ func Commands(con *console.SliverClient) []*cobra.Command {
// SessionIDCompleter completes session IDs.
func SessionIDCompleter(con *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err == nil {
for _, s := range sessions.Sessions {
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, s.ID[:8])
results = append(results, desc)
}
}
return carapace.ActionValuesDescribed(results...).Tag("sessions")
return carapace.ActionValuesDescribed(sessionCompletionPairs(con)...).Tag("sessions")
}
return carapace.ActionCallback(callback)
}
func registerSessionIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values := describedValuesToTabs(sessionCompletionPairs(con))
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func sessionCompletionPairs(con *console.SliverClient) []string {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results
}
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return results
}
for _, s := range sessions.Sessions {
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, s.ID[:8], desc)
}
return results
}
func describedValuesToTabs(values []string) []string {
tabbed := make([]string, 0, len(values)/2)
for i := 0; i+1 < len(values); i += 2 {
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
}
return tabbed
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
// SliverCommands returns all session control commands for the active target.
func SliverCommands(con *console.SliverClient) []*cobra.Command {
backgroundCmd := &cobra.Command{
+20 -20
View File
@@ -192,15 +192,15 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
})
for _, session := range sessions {
color := console.Normal
style := console.StyleNormal
if con.ActiveTarget.GetSession() != nil && con.ActiveTarget.GetSession().ID == session.ID {
color = console.Green
style = console.StyleGreen
}
var SessionHealth string
if session.IsDead {
SessionHealth = console.Bold + console.Red + "[DEAD]" + console.Normal
SessionHealth = console.StyleBoldRed.Render("[DEAD]")
} else {
SessionHealth = console.Bold + console.Green + "[ALIVE]" + console.Normal
SessionHealth = console.StyleBoldGreen.Render("[ALIVE]")
}
burned := ""
if session.Burned {
@@ -211,33 +211,33 @@ func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterR
var rowEntries []string
if wideTermWidth {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, username),
fmt.Sprintf(color+"%s (%d)"+console.Normal, session.Filename, session.PID),
style.Render(ShortSessionID(session.ID)),
style.Render(session.Name),
style.Render(session.Transport),
style.Render(session.RemoteAddress),
style.Render(session.Hostname),
style.Render(username),
style.Render(fmt.Sprintf("%s (%d)", session.Filename, session.PID)),
}
if windowsSessionInList {
rowEntries = append(rowEntries, fmt.Sprintf(color+"%s"+console.Normal, session.Integrity))
rowEntries = append(rowEntries, style.Render(session.Integrity))
}
rowEntries = append(rowEntries, []string{
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
fmt.Sprintf(color+"%s"+console.Normal, session.Locale),
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
style.Render(session.Locale),
con.FormatDateDelta(time.Unix(session.LastCheckin, 0), wideTermWidth, false),
burned + SessionHealth,
}...)
} else {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
fmt.Sprintf(color+"%s"+console.Normal, session.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, username),
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
style.Render(ShortSessionID(session.ID)),
style.Render(session.Transport),
style.Render(session.RemoteAddress),
style.Render(session.Hostname),
style.Render(username),
style.Render(fmt.Sprintf("%s/%s", session.OS, session.Arch)),
burned + SessionHealth,
}
}
+6 -1
View File
@@ -41,11 +41,16 @@ func SettingsAutoAdultCmd(cmd *cobra.Command, con *console.SliverClient, args []
// IsUserAnAdult - This should be called for any dangerous (OPSEC-wise) functions.
func IsUserAnAdult(con *console.SliverClient) bool {
return IsUserAnAdultWithPrompt(con, "This action is bad OPSEC, are you an adult?")
}
// IsUserAnAdultWithPrompt prompts with a custom message before continuing.
func IsUserAnAdultWithPrompt(con *console.SliverClient, prompt string) bool {
if GetAutoAdult(con) {
return true
}
confirm := false
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
_ = forms.Confirm(prompt, &confirm)
return confirm
}
+13
View File
@@ -20,6 +20,7 @@ package settings
import (
"errors"
"path/filepath"
"strconv"
"strings"
@@ -58,12 +59,22 @@ func SettingsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("Failed to apply settings form values: %s\n", err)
return
}
if err := assets.SaveSettings(con.Settings); err != nil {
con.PrintErrorf("Failed to save settings: %s\n", err)
return
}
if assets.NormalizePromptStyle(result.PromptStyle) == assets.PromptStyleCustom {
settingsPath := filepath.Join(assets.GetRootAppDir(), "tui-settings.yaml")
con.PrintInfof("Prompt style is %q. Edit %s to set %q.\n", assets.PromptStyleCustom, settingsPath, "prompt_template")
con.Println("")
}
}
tw := table.NewWriter()
tw.SetStyle(GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Value", "Description"})
tw.AppendRow(table.Row{"Tables", con.Settings.TableStyle, "Set the stylization of tables"})
tw.AppendRow(table.Row{"Prompt", con.Settings.PromptStyle, "Set the console prompt prefix style"})
tw.AppendRow(table.Row{"Auto Adult", con.Settings.AutoAdult, "Automatically accept OPSEC warnings"})
tw.AppendRow(table.Row{"Auto Beacon Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
@@ -221,6 +232,7 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
if width < 1 {
return errors.New("small terminal width must be 1 or greater")
}
promptStyle := assets.NormalizePromptStyle(result.PromptStyle)
settings.TableStyle = result.TableStyle
settings.AutoAdult = result.AutoAdult
@@ -230,5 +242,6 @@ func applySettingsForm(settings *assets.ClientSettings, result *forms.SettingsFo
settings.VimMode = result.VimMode
settings.UserConnect = result.UserConnect
settings.ConsoleLogs = result.ConsoleLogs
settings.PromptStyle = promptStyle
return nil
}
+2 -2
View File
@@ -218,12 +218,12 @@ func PaginateTable(tw table.Writer, skipPages int, overflow bool, interactive bo
}
con.Println()
} else {
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
}
}
} else {
if 1 < len(pages) {
con.Printf(console.Bold+"Page [%d/%d]\n", pageNumber+1, len(pages))
con.Printf("%s\n", console.StyleBold.Render(fmt.Sprintf("Page [%d/%d]", pageNumber+1, len(pages))))
}
break
}
+5 -2
View File
@@ -6,7 +6,10 @@ Implements the 'shell' command group for the Sliver client console. Handlers map
## Go Files
- `commands.go` – Exposes the interactive shell command and binds its options.
- `commands.go` – Exposes the shell command group (`shell`, `shell ls`, `shell attach`, `shell kill`) and binds options.
- `completers.go` – Shell-specific command completion helpers.
- `filter-reader_generic.go` – Provides output filtering for shell streams on POSIX targets.
- `filter-reader_windows.go` – Implements CRLF-aware filtering and decoding for Windows shell sessions.
- `shell.go` – Launches interactive command shells over RPC and manages IO loops.
- `ioloop.go` – Handles attached shell stdin forwarding, escape handling, and fast local detach/exit behavior.
- `manager.go` – Tracks managed shell tunnels so detached shells can be listed and re-attached.
- `shell.go` – Launches/attaches shell tunnels and coordinates managed shell lifecycle.
+42
View File
@@ -5,6 +5,7 @@ import (
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -28,5 +29,46 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellLsCmd := &cobra.Command{
Use: consts.LsStr,
Short: "List managed local shell tunnels",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Run: func(cmd *cobra.Command, args []string) {
ShellLsCmd(cmd, con, args)
},
}
shellCmd.AddCommand(shellLsCmd)
shellAttachCmd := &cobra.Command{
Use: "attach <id>",
Short: "Attach to a managed local shell tunnel",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellAttachCmd(cmd, con, args)
},
}
flags.Bind("", false, shellAttachCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellCmd.AddCommand(shellAttachCmd)
carapace.Gen(shellAttachCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
shellKillCmd := &cobra.Command{
Use: consts.KillStr + " <id>",
Short: "Kill a managed shell and clean up remote process state",
Long: help.GetHelpFor([]string{consts.ShellStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellKillCmd(cmd, con, args)
},
}
flags.Bind("", false, shellKillCmd, func(f *pflag.FlagSet) {
f.BoolP("force", "F", false, "force process cleanup via terminate rpc")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellCmd.AddCommand(shellKillCmd)
carapace.Gen(shellKillCmd).PositionalCompletion(ShellIDCompleter(con).Usage("managed shell ID"))
return []*cobra.Command{shellCmd}
}
+30
View File
@@ -0,0 +1,30 @@
package shell
import (
"fmt"
"strconv"
"github.com/bishopfox/sliver/client/console"
"github.com/rsteube/carapace"
)
// ShellIDCompleter completes IDs of managed local shells.
func ShellIDCompleter(_ *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
managed := shells.List()
if len(managed) == 0 {
return carapace.ActionMessage("no managed shells")
}
for _, sh := range managed {
results = append(results, strconv.Itoa(sh.ID))
results = append(results, fmt.Sprintf("%s, pid=%d, state=%s", sessionLabel(sh.SessionID, sh.SessionName), sh.Pid, sh.State()))
}
return carapace.ActionValuesDescribed(results...).Tag("managed shells")
}
return carapace.ActionCallback(callback)
}
+148
View File
@@ -0,0 +1,148 @@
package shell
import (
"bufio"
"bytes"
"io"
"log"
"os"
"strings"
"sync"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
)
// runAttachedIO forwards local stdin to a shell tunnel while attached to the shell.
// It returns:
// - detached=true when user hit escape (Ctrl-])
// - closeRequested=true when user requested shell close (EOF/exit/logout)
func runAttachedIO(tunnel *core.TunnelIO, con *console.SliverClient) (detached bool, closeRequested bool) {
// We can't pipe stdin directly into the tunnel: if the gRPC tunnel send path blocks
// (network hang, server gone, etc.) tunnel.Write can block forever and "lock" the
// client in shell mode. Instead, buffer stdin locally and look for a local escape key.
const (
shellEscapeByte = byte(0x1d) // Ctrl-]
stdinBufSize = 32 * 1024
stdinQueueDepth = 128
)
stdinQueue := make(chan []byte, stdinQueueDepth)
stopWriter := make(chan struct{})
var writerWG sync.WaitGroup
writerWG.Add(1)
go func() {
defer writerWG.Done()
defer func() {
// Tunnel teardown can race with writes (server-side close, disconnect, etc.).
// Don't let a send-on-closed-channel panic bring down the whole client.
if r := recover(); r != nil {
log.Printf("Shell tunnel writer stopped: %v", r)
}
}()
for {
select {
case <-stopWriter:
return
case data, ok := <-stdinQueue:
if !ok {
return
}
if len(data) == 0 {
continue
}
select {
case <-stopWriter:
return
case tunnel.Send <- data:
}
}
}
}()
defer func() {
close(stopWriter)
close(stdinQueue)
writerWG.Wait()
}()
stdin := newFilterReader(os.Stdin)
buf := make([]byte, stdinBufSize)
lineBuf := make([]byte, 0, 128)
inEscSeq := false
log.Printf("Reading from stdin (escape=Ctrl-]) ...")
for {
n, err := stdin.Read(buf)
if n > 0 {
data := buf[:n]
if i := bytes.IndexByte(data, shellEscapeByte); i >= 0 {
if i > 0 {
// Best-effort: don't block stdin if the tunnel is wedged.
dataCopy := append([]byte(nil), data[:i]...)
select {
case stdinQueue <- dataCopy:
default:
}
}
detached = true
break
}
exitRequested := false
for _, b := range data {
if inEscSeq {
if b >= 0x40 && b <= 0x7e {
inEscSeq = false
}
continue
}
switch b {
case '\r', '\n':
line := strings.TrimSpace(string(lineBuf))
if line == "exit" || line == "logout" {
exitRequested = true
}
lineBuf = lineBuf[:0]
case 0x1b:
inEscSeq = true
case 0x08, 0x7f:
if len(lineBuf) > 0 {
lineBuf = lineBuf[:len(lineBuf)-1]
}
default:
// Keep printable bytes only; ignore control sequences (arrows, etc.).
if b >= 0x20 && b <= 0x7e {
lineBuf = append(lineBuf, b)
}
}
}
dataCopy := append([]byte(nil), data...)
select {
case stdinQueue <- dataCopy:
default:
// Drop input if the tunnel send path is blocked; still allow escape.
}
if exitRequested {
closeRequested = true
break
}
}
if err != nil {
if err != io.EOF {
con.PrintErrorf("Error reading from stdin: %s\n", err)
}
closeRequested = true
break
}
}
log.Printf("Exit interactive")
bufio.NewWriter(os.Stdout).Flush()
return detached, closeRequested
}
+150
View File
@@ -0,0 +1,150 @@
package shell
import (
"io"
"log"
"sort"
"sync"
"time"
"github.com/bishopfox/sliver/client/core"
)
type shellManager struct {
mu sync.RWMutex
nextID int
shells map[int]*managedShell
}
func newShellManager() *shellManager {
return &shellManager{
nextID: 1,
shells: map[int]*managedShell{},
}
}
var shells = newShellManager()
type shellState string
const (
shellStateAttached shellState = "attached"
shellStateDetached shellState = "detached"
shellStateClosing shellState = "closing"
)
type managedShell struct {
ID int
SessionID string
SessionName string
Path string
Pid uint32
TunnelID uint64
EnablePTY bool
CreatedAt time.Time
mu sync.RWMutex
state shellState
tunnel *core.TunnelIO
output *swapWriter
readerWG sync.WaitGroup
}
func (s *managedShell) State() shellState {
s.mu.RLock()
defer s.mu.RUnlock()
return s.state
}
func (s *managedShell) setState(state shellState) {
s.mu.Lock()
defer s.mu.Unlock()
s.state = state
}
func (s *managedShell) Tunnel() *core.TunnelIO {
s.mu.RLock()
defer s.mu.RUnlock()
return s.tunnel
}
func (s *managedShell) SetOutput(w io.Writer) {
s.output.Set(w)
}
func (s *managedShell) startReader(remove func()) {
s.readerWG.Add(1)
go func() {
defer s.readerWG.Done()
_, err := io.Copy(s.output, s.tunnel)
log.Printf("Shell tunnel reader (id=%d tunnel=%d) exited: %v", s.ID, s.TunnelID, err)
remove()
}()
}
// swapWriter is an io.Writer whose underlying destination can be swapped at runtime.
// This is used so detached shells can keep draining output without clobbering the REPL.
type swapWriter struct {
mu sync.RWMutex
w io.Writer
}
func newSwapWriter(w io.Writer) *swapWriter {
return &swapWriter{w: w}
}
func (s *swapWriter) Set(w io.Writer) {
s.mu.Lock()
s.w = w
s.mu.Unlock()
}
func (s *swapWriter) Write(p []byte) (int, error) {
s.mu.RLock()
w := s.w
s.mu.RUnlock()
if w == nil {
return len(p), nil
}
return w.Write(p)
}
func (m *shellManager) Add(sh *managedShell) int {
m.mu.Lock()
defer m.mu.Unlock()
id := m.nextID
m.nextID++
sh.ID = id
m.shells[id] = sh
return id
}
func (m *shellManager) Remove(id int) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.shells, id)
}
func (m *shellManager) Get(id int) (*managedShell, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
sh, ok := m.shells[id]
return sh, ok
}
func (m *shellManager) List() []*managedShell {
m.mu.RLock()
defer m.mu.RUnlock()
results := make([]*managedShell, 0, len(m.shells))
for _, sh := range m.shells {
results = append(results, sh)
}
sort.Slice(results, func(i, j int) bool {
return results[i].ID < results[j].ID
})
return results
}
+97
View File
@@ -0,0 +1,97 @@
//go:build !windows
package shell
import (
"context"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
if con == nil || con.Rpc == nil {
return func() {}
}
stdoutFd := int(os.Stdout.Fd())
stdinFd := int(os.Stdin.Fd())
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
return func() {}
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGWINCH)
stopCh := make(chan struct{})
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
lastCols, lastRows := -1, -1
send := func() bool {
cols, rows, err := term.GetSize(stdoutFd)
if err != nil || cols <= 0 || rows <= 0 {
cols, rows, err = term.GetSize(stdinFd)
}
if err != nil || cols <= 0 || rows <= 0 {
return true
}
if cols == lastCols && rows == lastRows {
return true
}
lastCols, lastRows = cols, rows
req := con.ActiveTarget.Request(cmd)
if req == nil {
return false
}
req.Timeout = int64(2 * time.Second)
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
Request: req,
TunnelID: tunnelID,
Rows: uint32(rows),
Cols: uint32(cols),
})
if status.Code(err) == codes.Unimplemented {
return false
}
return true
}
if !send() {
return
}
for {
select {
case <-stopCh:
return
case <-sigCh:
if !send() {
return
}
}
}
}()
var once sync.Once
return func() {
once.Do(func() {
signal.Stop(sigCh)
close(stopCh)
<-doneCh
})
}
}
+93
View File
@@ -0,0 +1,93 @@
//go:build windows
package shell
import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"os"
"sync"
"time"
)
func startPtyResizeWatcher(con *console.SliverClient, cmd *cobra.Command, tunnelID uint64) func() {
if con == nil || con.Rpc == nil {
return func() {}
}
stdoutFd := int(os.Stdout.Fd())
stdinFd := int(os.Stdin.Fd())
if !term.IsTerminal(stdoutFd) && !term.IsTerminal(stdinFd) {
return func() {}
}
stopCh := make(chan struct{})
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
lastCols, lastRows := -1, -1
send := func() bool {
cols, rows, err := term.GetSize(stdoutFd)
if err != nil || cols <= 0 || rows <= 0 {
cols, rows, err = term.GetSize(stdinFd)
}
if err != nil || cols <= 0 || rows <= 0 {
return true
}
if cols == lastCols && rows == lastRows {
return true
}
lastCols, lastRows = cols, rows
req := con.ActiveTarget.Request(cmd)
if req == nil {
return false
}
req.Timeout = int64(2 * time.Second)
_, err = con.Rpc.ShellResize(context.Background(), &sliverpb.ShellResizeReq{
Request: req,
TunnelID: tunnelID,
Rows: uint32(rows),
Cols: uint32(cols),
})
if status.Code(err) == codes.Unimplemented {
return false
}
return true
}
if !send() {
return
}
for {
select {
case <-stopCh:
return
case <-ticker.C:
if !send() {
return
}
}
}
}()
var once sync.Once
return func() {
once.Do(func() {
close(stopCh)
<-doneCh
})
}
}
+350 -68
View File
@@ -19,24 +19,34 @@ package shell
*/
import (
"bufio"
"context"
"io"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"golang.org/x/term"
)
const (
windows = "windows"
darwin = "darwin"
linux = "linux"
darwin = "darwin"
linux = "linux"
)
type shellResult int
const (
shellExited shellResult = iota
shellDetached
shellAttachFailed
)
// ShellCmd - Start an interactive shell on the remote system.
@@ -55,92 +65,364 @@ func ShellCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != darwin {
noPty = true // Sliver's PTYs are only supported on linux/darwin
}
runInteractive(cmd, shellPath, noPty, con)
con.Println("Shell exited")
result := runInteractive(cmd, shellPath, noPty, con, nil)
if result == shellDetached {
con.Println("Shell detached")
} else if result == shellExited {
con.Println("Shell exited")
}
}
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient) {
// ShellLsCmd lists local shell tunnels currently managed by the client.
func ShellLsCmd(_ *cobra.Command, con *console.SliverClient, _ []string) {
managed := shells.List()
if len(managed) == 0 {
con.PrintInfof("No managed shells\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"State",
"Session",
"PID",
"PTY",
"Tunnel ID",
"Path",
"Age",
})
now := time.Now()
for _, sh := range managed {
age := now.Sub(sh.CreatedAt).Round(time.Second).String()
if sh.CreatedAt.IsZero() {
age = "-"
}
pid := "-"
if sh.Pid > 0 {
pid = strconv.FormatUint(uint64(sh.Pid), 10)
}
tw.AppendRow(table.Row{
sh.ID,
sh.State(),
sessionLabel(sh.SessionID, sh.SessionName),
pid,
sh.EnablePTY,
sh.TunnelID,
sh.Path,
age,
})
}
con.Printf("%s\n", tw.Render())
}
// ShellAttachCmd re-attaches local stdin/stdout to a managed shell tunnel.
func ShellAttachCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(args) < 1 {
con.PrintErrorf("Usage: shell attach <id>\n")
return
}
shellID, err := strconv.Atoi(args[0])
if err != nil || shellID <= 0 {
con.PrintErrorf("Invalid shell id: %s\n", args[0])
return
}
sh, ok := shells.Get(shellID)
if !ok {
con.PrintErrorf("Unknown shell id: %d\n", shellID)
return
}
if sh.State() == shellStateAttached {
con.PrintErrorf("Shell %d is already attached\n", shellID)
return
}
if sh.State() == shellStateClosing {
con.PrintErrorf("Shell %d is closing\n", shellID)
return
}
tunnel := sh.Tunnel()
if tunnel == nil || !tunnel.IsOpen() {
con.PrintErrorf("Shell %d is no longer active\n", shellID)
shells.Remove(shellID)
return
}
session := con.ActiveTarget.GetSession()
if session == nil || session.ID != sh.SessionID {
con.PrintErrorf("Shell %d belongs to %s; switch targets before attaching\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
return
}
result := runInteractive(cmd, sh.Path, !sh.EnablePTY, con, sh)
if result == shellDetached {
con.Println("Shell detached")
} else if result == shellExited {
con.Println("Shell exited")
}
}
// ShellKillCmd kills a managed shell from the REPL and cleans up remote state.
func ShellKillCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(args) < 1 {
con.PrintErrorf("Usage: shell kill <id>\n")
return
}
shellID, err := strconv.Atoi(args[0])
if err != nil || shellID <= 0 {
con.PrintErrorf("Invalid shell id: %s\n", args[0])
return
}
force, _ := cmd.Flags().GetBool("force")
sh, ok := shells.Get(shellID)
if !ok {
con.PrintErrorf("Unknown shell id: %d\n", shellID)
return
}
session := con.ActiveTarget.GetSession()
if session == nil || session.ID != sh.SessionID {
con.PrintErrorf("Shell %d belongs to %s; switch targets before killing\n", shellID, sessionLabel(sh.SessionID, sh.SessionName))
return
}
sh.setState(shellStateClosing)
sh.SetOutput(io.Discard)
shells.Remove(sh.ID)
// Best-effort graceful request before closing the tunnel.
requestRemoteShellExit(sh.Tunnel())
if err := closeShellTunnel(con, sh.TunnelID, sh.SessionID); err != nil {
con.PrintWarnf("Error closing shell tunnel %d: %s\n", sh.TunnelID, err)
}
if force && sh.Pid > 1 {
terminate, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{
Request: con.ActiveTarget.Request(cmd),
Pid: int32(sh.Pid),
Force: force,
})
if err != nil {
con.PrintWarnf("Error terminating shell process %d: %s\n", sh.Pid, err)
} else if terminate.Response != nil && terminate.Response.GetErr() != "" {
errMsg := terminate.Response.GetErr()
// If shell already exited, that is an acceptable cleanup outcome.
if strings.Contains(strings.ToLower(errMsg), "no such process") ||
strings.Contains(strings.ToLower(errMsg), "process not found") {
con.PrintInfof("Shell process %d already exited\n", sh.Pid)
} else {
con.PrintWarnf("Terminate response for shell process %d: %s\n", sh.Pid, errMsg)
}
} else {
con.PrintInfof("Shell process %d terminated\n", sh.Pid)
}
}
if force {
con.PrintInfof("Shell %d killed (force)\n", sh.ID)
} else {
con.PrintInfof("Shell %d killed\n", sh.ID)
}
}
func runInteractive(cmd *cobra.Command, shellPath string, noPty bool, con *console.SliverClient, existing *managedShell) shellResult {
con.Println()
con.PrintInfof("Wait approximately 10 seconds after exit, and press <enter> to continue\n")
con.PrintInfof("Opening shell tunnel (EOF to exit) ...\n\n")
con.PrintInfof("Shell management: `shell ls`, `shell attach <id>`\n")
con.PrintInfof("Escape: press Ctrl-] to return to the Sliver client\n")
session := con.ActiveTarget.GetSession()
if session == nil {
return
return shellAttachFailed
}
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
defer cancelTunnel()
if err != nil {
con.PrintErrorf("%s\n", err)
return
if existing == nil {
con.PrintInfof("Opening shell tunnel ...\n\n")
} else {
con.PrintInfof("Re-attaching to shell %d ...\n\n", existing.ID)
}
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object
tunnel := core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
var (
tunnel *core.TunnelIO
managed *managedShell
enablePTY bool
oldState *term.State
stateSaved bool
stopPtySize = func() {}
err error
)
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(cmd),
Path: shellPath,
EnablePTY: !noPty,
TunnelID: tunnel.ID,
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
//
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnel.ID,
if existing == nil {
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel.
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
defer cancelTunnel()
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("RPC Error: %s\n", err)
con.PrintErrorf("%s\n", err)
return shellAttachFailed
}
return
}
defer tunnel.Close()
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
var oldState *term.State
if !noPty {
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object.
tunnel = core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
var rows uint32
var cols uint32
if !noPty {
colsInt, rowsInt, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || rowsInt <= 0 || colsInt <= 0 {
colsInt, rowsInt, err = term.GetSize(int(os.Stdin.Fd()))
}
if err == nil && rowsInt > 0 && colsInt > 0 {
rows = uint32(rowsInt)
cols = uint32(colsInt)
}
}
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(cmd),
Path: shellPath,
EnablePTY: !noPty,
Rows: rows,
Cols: cols,
TunnelID: tunnel.ID,
})
if err != nil {
con.PrintErrorf("%s\n", err)
core.GetTunnels().Close(tunnel.ID)
return shellAttachFailed
}
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
go backgroundCloseShell(con, tunnel.ID, session.ID)
return shellAttachFailed
}
path := shellPath
if path == "" {
path = shell.Path
}
enablePTY = !noPty
managed = &managedShell{
SessionID: session.ID,
SessionName: session.Name,
Path: path,
Pid: shell.Pid,
TunnelID: tunnel.ID,
EnablePTY: enablePTY,
CreatedAt: time.Now(),
state: shellStateAttached,
tunnel: tunnel,
output: newSwapWriter(os.Stdout),
}
managed.ID = shells.Add(managed)
managed.startReader(func() {
shells.Remove(managed.ID)
})
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell [%d] with pid %d\n\n", managed.ID, shell.Pid)
} else {
managed = existing
tunnel = managed.Tunnel()
enablePTY = managed.EnablePTY
managed.setState(shellStateAttached)
managed.SetOutput(os.Stdout)
con.PrintInfof("Attached shell [%d] (pid %d)\n\n", managed.ID, managed.Pid)
}
if enablePTY {
oldState, err = term.MakeRaw(0)
log.Printf("Saving terminal state: %v", oldState)
if err != nil {
con.PrintErrorf("Failed to save terminal state")
return
con.PrintErrorf("Failed to save terminal state\n")
managed.SetOutput(io.Discard)
managed.setState(shellStateDetached)
return shellAttachFailed
}
stateSaved = true
stopPtySize = startPtyResizeWatcher(con, cmd, tunnel.ID)
}
defer stopPtySize()
if stateSaved {
defer func() {
log.Printf("Restoring terminal state ...")
term.Restore(0, oldState)
}()
}
log.Printf("Starting stdin/stdout shell ...")
go func() {
n, err := io.Copy(os.Stdout, tunnel)
log.Printf("Wrote %d bytes to stdout", n)
if err != nil {
con.PrintErrorf("Error writing to stdout: %s", err)
return
}
}()
log.Printf("Reading from stdin ...")
n, err := io.Copy(tunnel, newFilterReader(os.Stdin))
log.Printf("Read %d bytes from stdin", n)
if err != nil && err != io.EOF {
con.PrintErrorf("Error reading from stdin: %s\n", err)
detached, _ := runAttachedIO(tunnel, con)
if detached {
managed.SetOutput(io.Discard)
managed.setState(shellStateDetached)
return shellDetached
}
if !noPty {
log.Printf("Restoring terminal state ...")
term.Restore(0, oldState)
}
managed.SetOutput(io.Discard)
managed.setState(shellStateClosing)
shells.Remove(managed.ID)
log.Printf("Exit interactive")
bufio.NewWriter(os.Stdout).Flush()
// "exit" should return immediately; tunnel close/cleanup is best-effort in the background.
go backgroundCloseShell(con, tunnel.ID, session.ID)
return shellExited
}
func backgroundCloseShell(con *console.SliverClient, tunnelID uint64, sessionID string) {
err := closeShellTunnel(con, tunnelID, sessionID)
if err != nil {
log.Printf("Background close tunnel %d failed: %v", tunnelID, err)
}
}
func closeShellTunnel(con *console.SliverClient, tunnelID uint64, sessionID string) error {
core.GetTunnels().Close(tunnelID)
if con == nil || con.Rpc == nil {
return nil
}
_, err := con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnelID,
SessionID: sessionID,
})
return err
}
func requestRemoteShellExit(tunnel *core.TunnelIO) {
if tunnel == nil || !tunnel.IsOpen() {
return
}
for _, payload := range [][]byte{[]byte("exit\n"), []byte("logout\n")} {
func(data []byte) {
defer func() {
if r := recover(); r != nil {
log.Printf("Shell exit write skipped: %v", r)
}
}()
select {
case tunnel.Send <- append([]byte(nil), data...):
default:
}
}(payload)
}
}
func sessionLabel(sessionID, sessionName string) string {
if sessionName == "" {
return sessionID
}
return sessionName + " (" + sessionID + ")"
}
@@ -0,0 +1,55 @@
package shellcodeencoders
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the shellcode-encoders command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
shellcodeEncodersCmd := &cobra.Command{
Use: consts.ShellcodeEncodersStr,
Short: "List supported shellcode encoders",
Long: help.GetHelpFor([]string{consts.ShellcodeEncodersStr}),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
ShellcodeEncodersCmd(cmd, con, args)
},
GroupID: consts.PayloadsHelpGroup,
}
flags.Bind("shellcode-encoders", false, shellcodeEncodersCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellcodeEncodersEncodeCmd := &cobra.Command{
Use: "encode <shellcode file>...",
Short: "Encode shellcode using server encoders",
Long: help.GetHelpFor([]string{consts.ShellcodeEncodersStr, "encode"}),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellcodeEncodersEncodeCmd(cmd, con, args)
},
}
flags.Bind("shellcode-encoders encode", false, shellcodeEncodersEncodeCmd, func(f *pflag.FlagSet) {
f.StringP("encoder", "e", "", "encoder to use (shikata_ga_nai, xor, xor_dynamic)")
f.StringP("arch", "a", "amd64", "architecture of the shellcode")
f.IntP("iterations", "i", 1, "number of encoding iterations")
f.StringP("bad-chars", "b", "", "hex encoded bad characters to avoid (e.g. 0001)")
f.StringP("output", "o", "", "output file (or directory when encoding multiple files)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(shellcodeEncodersEncodeCmd, func(comp *carapace.ActionMap) {
(*comp)["encoder"] = ShellcodeEncoderNameCompleter(con)
(*comp)["arch"] = ShellcodeEncoderArchCompleter(con)
(*comp)["output"] = carapace.ActionFiles().Tag("output file/directory")
})
carapace.Gen(shellcodeEncodersEncodeCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
shellcodeEncodersCmd.AddCommand(shellcodeEncodersEncodeCmd)
return []*cobra.Command{shellcodeEncodersCmd}
}
+166
View File
@@ -0,0 +1,166 @@
package shellcodeencoders
import (
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
// ShellcodeEncodersEncodeCmd - Encode shellcode files with server encoders.
func ShellcodeEncodersEncodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
encoderFlag, _ := cmd.Flags().GetString("encoder")
if strings.TrimSpace(encoderFlag) == "" {
con.PrintErrorf("Encoder is required (use --encoder). See %s for options.\n", consts.ShellcodeEncodersStr)
return
}
encoderName := normalizeShellcodeEncoderName(encoderFlag)
encoderEnum, ok := shellcodeEncoderEnum(encoderName)
if !ok {
con.PrintErrorf("Unknown encoder %q (see %s for supported encoders)\n", encoderFlag, consts.ShellcodeEncodersStr)
return
}
archFlag, _ := cmd.Flags().GetString("arch")
arch := normalizeShellcodeArch(archFlag)
if arch == "" {
arch = "amd64"
}
iterations, _ := cmd.Flags().GetInt("iterations")
if iterations <= 0 {
iterations = 1
}
badCharsRaw, _ := cmd.Flags().GetString("bad-chars")
badChars, err := decodeHexBytes("bad-chars", badCharsRaw)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
output, _ := cmd.Flags().GetString("output")
outputInfo, err := outputInfoForArgs(output, len(args))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
grpcCtx, cancel := con.GrpcContext(cmd)
defer cancel()
hadError := false
for _, path := range args {
data, err := os.ReadFile(path)
if err != nil {
con.PrintErrorf("Failed to read %s: %s\n", path, err)
hadError = true
continue
}
resp, err := con.Rpc.ShellcodeEncoder(grpcCtx, &clientpb.ShellcodeEncodeReq{
Encoder: encoderEnum,
Architecture: arch,
Iterations: uint32(iterations),
BadChars: badChars,
Data: data,
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
con.PrintErrorf("Failed to encode %s: %s\n", path, err)
hadError = true
continue
}
if resp.GetResponse() != nil && resp.GetResponse().GetErr() != "" {
con.PrintErrorf("Failed to encode %s: %s\n", path, resp.GetResponse().GetErr())
hadError = true
continue
}
outputPath, err := resolveOutputPath(path, output, outputInfo, encoderName, len(args) > 1)
if err != nil {
con.PrintErrorf("Failed to write %s: %s\n", path, err)
hadError = true
continue
}
if err := os.WriteFile(outputPath, resp.Data, 0o644); err != nil {
con.PrintErrorf("Failed to write %s: %s\n", outputPath, err)
hadError = true
continue
}
con.PrintInfof("Shellcode written to %s (%d bytes)\n", outputPath, len(resp.Data))
}
if hadError {
con.PrintWarnf("One or more files failed to encode\n")
}
}
func decodeHexBytes(label string, raw string) ([]byte, error) {
trimmed := strings.TrimSpace(raw)
trimmed = strings.TrimPrefix(trimmed, "0x")
trimmed = strings.TrimPrefix(trimmed, "0X")
trimmed = strings.ReplaceAll(trimmed, " ", "")
if trimmed == "" {
return nil, nil
}
data, err := hex.DecodeString(trimmed)
if err != nil {
return nil, fmt.Errorf("%s must be hex encoded: %w", label, err)
}
return data, nil
}
func outputInfoForArgs(output string, argCount int) (os.FileInfo, error) {
if output == "" {
return nil, nil
}
info, err := os.Stat(output)
if err != nil {
if os.IsNotExist(err) {
if argCount > 1 {
if mkErr := os.MkdirAll(output, 0o755); mkErr != nil {
return nil, fmt.Errorf("failed to create output directory %s: %w", output, mkErr)
}
info, err = os.Stat(output)
if err != nil {
return nil, fmt.Errorf("failed to stat output directory %s: %w", output, err)
}
return info, nil
}
return nil, nil
}
return nil, err
}
if argCount > 1 && !info.IsDir() {
return nil, fmt.Errorf("output must be a directory when encoding multiple files")
}
return info, nil
}
func resolveOutputPath(inputPath string, output string, outputInfo os.FileInfo, encoderName string, multiple bool) (string, error) {
suffix := "." + strings.ReplaceAll(encoderName, "_", "-")
base := filepath.Base(inputPath)
if output == "" {
return filepath.Join(filepath.Dir(inputPath), base+suffix), nil
}
if multiple {
if outputInfo == nil || !outputInfo.IsDir() {
return "", fmt.Errorf("output must be a directory when encoding multiple files")
}
return filepath.Join(output, base+suffix), nil
}
if outputInfo != nil && outputInfo.IsDir() {
return filepath.Join(output, base+suffix), nil
}
return output, nil
}
@@ -0,0 +1,103 @@
package shellcodeencoders
import (
"fmt"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
)
var shellcodeEncoderNameToEnum = map[string]clientpb.ShellcodeEncoder{
"shikata_ga_nai": clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
"xor": clientpb.ShellcodeEncoder_XOR,
"xor_dynamic": clientpb.ShellcodeEncoder_XOR_DYNAMIC,
}
func normalizeShellcodeEncoderName(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "_")
return normalized
}
func normalizeShellcodeArch(arch string) string {
normalized := strings.ToLower(strings.TrimSpace(arch))
switch normalized {
case "amd64", "x64", "x86_64":
return "amd64"
case "386", "x86", "i386":
return "386"
case "arm64", "aarch64":
return "arm64"
default:
return normalized
}
}
func shellcodeEncoderEnum(name string) (clientpb.ShellcodeEncoder, bool) {
normalized := normalizeShellcodeEncoderName(name)
encoder, ok := shellcodeEncoderNameToEnum[normalized]
return encoder, ok
}
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
grpcCtx, cancel := con.GrpcContext(nil)
defer cancel()
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
}
// ShellcodeEncoderNameCompleter returns available encoder names with supported arches.
func ShellcodeEncoderNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return carapace.ActionMessage("failed to fetch shellcode encoders: %s", err.Error())
}
nameArchs := map[string][]string{}
for arch, archMap := range encoderMap.GetEncoders() {
if archMap == nil {
continue
}
for name := range archMap.GetEncoders() {
nameArchs[name] = append(nameArchs[name], arch)
}
}
names := make([]string, 0, len(nameArchs))
for name := range nameArchs {
names = append(names, name)
}
sort.Strings(names)
results := []string{}
for _, name := range names {
arches := nameArchs[name]
sort.Strings(arches)
desc := fmt.Sprintf("(%s)", strings.Join(arches, ", "))
results = append(results, name, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("shellcode encoders")
})
}
// ShellcodeEncoderArchCompleter returns available architectures.
func ShellcodeEncoderArchCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return carapace.ActionMessage("failed to fetch shellcode encoders: %s", err.Error())
}
arches := make([]string, 0, len(encoderMap.GetEncoders()))
for arch := range encoderMap.GetEncoders() {
arches = append(arches, arch)
}
sort.Strings(arches)
return carapace.ActionValues(arches...).Tag("shellcode architectures")
})
}
Loaded 100 of 4902 files, more files were not shown because too many files have changed in this diff. Show more