Compare commits

...
Author SHA1 Message Date
moloch-- 6a7bd0803a Update --save help 2026-01-28 17:21:35 -08:00
moloch-- 6c70c1c1e6 Add signature validation logic 2026-01-28 17:20:24 -08:00
moloch-- ddc222b8a2 Improvements to update 2026-01-28 17:07:47 -08:00
moloch-- 5506a582ac Add SliverPublicKey 2026-01-28 16:45:32 -08:00
Joe 63d872c98b Merge pull request #2142 from BishopFox/fix/sgn
Fix/sgn
2026-01-28 17:31:46 -07:00
moloch-- ef9f5dde0d Fix unit tests 2026-01-28 07:21:38 -08:00
moloch-- e35b7fdb97 Update sgn to v0.0.5 2026-01-27 21:28:21 -08:00
moloch-- efbbf382eb Merge branch 'master' into fix/sgn 2026-01-27 21:26:42 -08:00
moloch-- 0b8d00900d v1.6.7 2026-01-27 21:06:39 -08:00
moloch-- 9077596f25 Damn it Claude 2026-01-27 21:04:30 -08:00
moloch-- 0a8104a32e Damn it Claude 2026-01-27 21:03:25 -08:00
moloch-- 15a0ba4c1e Damn it Claude 2026-01-27 21:01:55 -08:00
moloch-- d452c8a676 Write .bin even if sgn fails 2026-01-27 20:49:28 -08:00
Joe fb74b491fa Merge pull request #2141 from BishopFox/fix/console-inputs
Update readline/console deps
2026-01-27 21:38:20 -07:00
moloch-- 2af6e11adc Update readline/console deps 2026-01-27 19:55:18 -08:00
Joe a3a98858fd Merge pull request #2135 from BishopFox/fix/sqlite
Add sqlite pragmas to db config
2026-01-21 22:28:56 -07:00
moloch-- d0652f2fd3 Add sqlite pragmas to db config 2026-01-21 16:08:07 -08:00
Joe 936f067363 Merge pull request #2130 from BishopFox/feature/website-info
Feature/website info
2026-01-21 10:25:56 -07:00
moloch-- 041c3c7048 Improve tab completer for websites cmd 2026-01-20 21:29:39 -08:00
moloch-- b48f74af58 Add original file name and sha256 to websites cmd 2026-01-20 19:13:42 -08:00
moloch-- 6ec8849b20 v1.6.6 2026-01-16 09:18:39 -08:00
Joe db7cf1ed62 Merge pull request #2125 from BishopFox/fix/winlsdir
Improved `err` and `nil` checks in `dirListHandler`
2026-01-16 10:17:01 -07:00
moloch-- 16e1e77068 Implement better err and nil checks in dirListHandler issue #2122 2026-01-16 07:06:43 -08:00
moloch-- 1cfa694aa2 Merge branch 'update/deps' into tui/adv-forms 2026-01-16 06:57:06 -08:00
moloch-- 5898877136 Fix --rc scripts 2026-01-16 06:56:41 -08:00
moloch-- a7d8cac565 Update generate form 2026-01-16 06:41:58 -08:00
moloch-- 0e73afca47 Update deps 2026-01-16 06:07:10 -08:00
moloch-- 432941d694 Tab complete parent commands from submenu 2026-01-16 05:58:35 -08:00
moloch-- ac521bf53c v1.6.4 2026-01-15 16:10:06 -08:00
Joe 9720c9f14f Merge pull request #2121 from BishopFox/feature/flag-completers
Improved flag completers
2026-01-15 17:09:30 -07:00
moloch-- 4a224fdb9c Improve completers for --interact flags 2026-01-15 15:14:20 -08:00
moloch-- 671eb4d777 Add tab completer to 'use' cmd 2026-01-15 15:03:21 -08:00
moloch-- 16cf816610 Implement os and arch completers 2026-01-15 13:54:21 -08:00
moloch-- 879f576c6e Add MCP to docs 2026-01-15 08:05:02 -08:00
moloch-- 2df78cdbf5 Update readme 2026-01-15 07:45:49 -08:00
moloch-- a05ffc0ae1 Fix footer/card z-index issue in docs site 2026-01-15 07:44:30 -08:00
moloch-- 6b4b218f09 Tweak docs 2026-01-15 07:33:40 -08:00
Joe 4d0d46afd6 Merge pull request #2118 from BishopFox/exec/env
Updates to `execute`
2026-01-13 22:45:02 -07:00
moloch-- f24c057503 Implement --env and --env-inheritance for 'execute' to allow greater control over subprocess env 2026-01-13 20:55:20 -08:00
moloch-- 7e3c1f1549 v1.6.3 2026-01-13 16:28:27 -08:00
Joe 1d4c0ec8a5 Merge pull request #2106 from cmprmsd/feature/extension-alias-autocompletion
Add argument completion for aliases and extensions
2026-01-13 17:25:11 -07:00
Joe 92dda6aa4f Merge pull request #2112 from BishopFox/6b37dependabot/go_modules/google.golang.org/protobuf-1.36.11
Bump google.golang.org/protobuf from 1.36.10 to 1.36.11
2026-01-13 17:24:30 -07:00
Joe 9a605dfda6 Merge pull request #2113 from BishopFox/0f1edependabot/go_modules/golang.org/x/sys-0.40.0
Bump golang.org/x/sys from 0.39.0 to 0.40.0
2026-01-13 17:24:14 -07:00
Joe e918885556 Merge pull request #2114 from Gubarz/getsystem
Fix: getsystem shellcode generation
2026-01-13 17:24:00 -07:00
Joe 263702594f Merge pull request #2115 from Gubarz/execute-assembly
Revert go-donut from 2022 patch to 2021
2026-01-13 17:23:17 -07:00
Joe 72b8dfb454 Merge pull request #2116 from BishopFox/fix/armory-all
Fix armory install all cmd
2026-01-13 17:23:00 -07:00
moloch-- aa2383ac24 Fix armory install all cmd 2026-01-12 21:45:21 -08:00
dependabot[bot] b6f0ea013d Bump golang.org/x/sys from 0.39.0 to 0.40.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/sys/compare/v0.39.0...v0.40.0)

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-12 03:05:34 +00:00
cmprmsd a903d2f596 Add support for predefined argument completion choices. in extensions and aliases
Example:
{
      "name": "browser",
      "desc": "target browser (chrome, edge, brave, all)",
      "type": "string",
      "optional": true,
      "choices": ["chrome", "edge", "brave", "all"]
    },
2026-01-11 22:11:40 +01:00
cmprmsd 4a4bb683e3 Add argument completion for aliases and extensions
- Introduced `AliasArgument` struct to define arguments for alias commands.
- Enhanced `LoadAlias` function to build usage and help messages including argument details.
- Implemented argument completion for alias commands using Carapace.
- Updated `makeAliasArgCompleter` to support positional and flag-style argument completions.
- Refactored `makeExtensionArgCompleter` to include similar completion features for extension commands.
- Improved user experience by displaying argument descriptions in completion lists one argument per line (instead of grouped).
2026-01-09 15:46:32 +01:00
moloch-- 016299c756 v1.6.2 2026-01-08 12:56:41 -08:00
Joe 075caaccda Merge pull request #2104 from BishopFox/rc/flush
Flush output on exit
2026-01-08 13:54:54 -07:00
Joe 729be253d3 Merge branch 'master' into rc/flush 2026-01-08 13:12:05 -07:00
moloch-- 13bd48182f Cleanup exit msg 2026-01-08 12:11:50 -08:00
moloch-- 67188dabea Flush output on exit 2026-01-08 12:10:30 -08:00
Joe 6e4c38c10e Merge pull request #2103 from BishopFox/fix-rportfwd
Fix rportfwd
2026-01-08 12:48:28 -07:00
Joe 00ecb1f4fd Merge pull request #2094 from BishopFox/5032dependabot/go_modules/github.com/spf13/cobra-1.10.2
Bump github.com/spf13/cobra from 1.10.1 to 1.10.2
2026-01-08 12:21:04 -07:00
Joe bac047fedd Merge pull request #2102 from 13621/revertpr2070
Fix DNS beacon closing immediately after registering
2026-01-08 12:20:48 -07:00
rkervella 10c16be351 Fix rportfwd 2026-01-08 10:48:57 -08:00
13621 d51ea9dbab fix: revert pr2070
Signed-off-by: 13621 <[email protected]>
2026-01-07 18:35:09 +00:00
dependabot[bot] 0eb41094de Bump github.com/spf13/cobra from 1.10.1 to 1.10.2
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-05 06:05:01 +00:00
Joe a219a97cb4 Merge pull request #2092 from BishopFox/3cabdependabot/go_modules/filippo.io/age-1.3.1
Bump filippo.io/age from 1.2.1 to 1.3.1
2026-01-04 23:04:08 -07:00
Joe b617bb4cf4 Merge pull request #2095 from BishopFox/21cddependabot/go_modules/github.com/ncruces/go-sqlite3-0.30.4
Bump github.com/ncruces/go-sqlite3 from 0.29.1 to 0.30.4
2026-01-04 23:03:54 -07:00
Joe 613b53430b Merge pull request #2093 from BishopFox/eb28dependabot/go_modules/github.com/miekg/dns-1.1.69
Bump github.com/miekg/dns from 1.1.68 to 1.1.69
2026-01-04 23:03:39 -07:00
Joe 472de54757 Merge pull request #2096 from BishopFox/b1e7dependabot/go_modules/golang.org/x/mod-0.31.0
Bump golang.org/x/mod from 0.30.0 to 0.31.0
2026-01-04 23:03:26 -07:00
Joe bdbc3bd7b5 Merge pull request #2090 from AnvithLobo/fix-forms
Fix interactive prompt cancellation handling
2026-01-04 23:03:11 -07:00
dependabot[bot] 87db61bcfb Bump github.com/miekg/dns from 1.1.68 to 1.1.69
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.68 to 1.1.69.
- [Commits](https://github.com/miekg/dns/compare/v1.1.68...v1.1.69)

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-05 05:13:43 +00:00
dependabot[bot] 095eb2722b Bump golang.org/x/mod from 0.30.0 to 0.31.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/mod/compare/v0.30.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-05 05:13:43 +00:00
dependabot[bot] abbb0da434 Bump github.com/ncruces/go-sqlite3 from 0.29.1 to 0.30.4
Bumps [github.com/ncruces/go-sqlite3](https://github.com/ncruces/go-sqlite3) from 0.29.1 to 0.30.4.
- [Release notes](https://github.com/ncruces/go-sqlite3/releases)
- [Commits](https://github.com/ncruces/go-sqlite3/compare/v0.29.1...v0.30.4)

---
updated-dependencies:
- dependency-name: github.com/ncruces/go-sqlite3
  dependency-version: 0.30.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-05 05:13:41 +00:00
dependabot[bot] 52f7484a71 Bump filippo.io/age from 1.2.1 to 1.3.1
Bumps [filippo.io/age](https://github.com/FiloSottile/age) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/FiloSottile/age/releases)
- [Commits](https://github.com/FiloSottile/age/compare/v1.2.1...v1.3.1)

---
updated-dependencies:
- dependency-name: filippo.io/age
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-01-05 05:13:38 +00:00
moloch-- a79b5fd7b9 go mod tidy 2026-01-04 21:11:55 -08:00
Joe 3e98d2d7a1 Merge pull request #2091 from BishopFox/feature/rc
Implement --rc for scripting initial inputs
2026-01-04 20:31:55 -07:00
moloch-- 13bf9d0a01 Implement --rc for scripting initial inputs 2026-01-04 17:51:59 -08:00
AnvithLobo 7aae725b88 Set form width dynamically based on terminal size
Introduced getTerminalWidth to determine the terminal width  and set the form width accordingly
2026-01-05 05:31:56 +05:30
AnvithLobo afd01073e4 Handle selection errors in builder and encoder prompts
Added error handling for empty selections in external builder and traffic encoder selection dialogs. Now returns a specific error or prints an error message if no selection is made
2026-01-05 04:58:53 +05:30
AnvithLobo ae0c33f41b Handle no selection in C2 profile selection
Updated the selectC2Profile function to return an error if no profile is selected, and modified callers to handle this error gracefully.
2026-01-05 04:47:40 +05:30
AnvithLobo 18da00b449 Fix: Allow user to cancel select prompts
Saves the original value before running the select prompt and restores it if the form is cancelled or an error occurs. This prevents unintended return value when the prompt was cancelled.
2026-01-05 04:39:11 +05:30
moloch-- dc736f6095 Update readme 2026-01-04 12:18:58 -08:00
Joe e02d53ce67 Merge pull request #2088 from BishopFox/fix/readline
Reduce flickering text in readline
2026-01-04 13:02:14 -07:00
Joe 064cd6a2cc Merge pull request #2087 from BishopFox/fix/mods3
Rename implant/sliver imports
2026-01-04 12:00:46 -07:00
moloch-- eab9d0b653 Reduce flickering text in readline 2026-01-04 10:59:20 -08:00
Joe 29c4ec0afa Merge pull request #2032 from carterburn/feature/aka-command
New command: add command aliases (aka)
2026-01-04 11:19:15 -07:00
moloch-- b7c336cb76 Rename implant/sliver imports 2026-01-04 10:17:56 -08:00
moloch-- d8ddd09c86 Update pretty table 2026-01-04 09:42:44 -08:00
Joe 04bb356207 Merge branch 'master' into feature/aka-command 2026-01-04 10:19:52 -07:00
Joe a003a5e3cf Merge pull request #2086 from AnvithLobo/master
Fix beacon panic race condition
2026-01-04 09:19:53 -07:00
Joe 75e3cad76a Merge branch 'master' into feature/aka-command 2026-01-04 09:06:36 -07:00
AnvithLobo 8aa0cbee7e Fix beacon panic race condition
Tasks are now dispatched to background goroutines, and their results are collected asynchronously via a channel. Check-ins include any pending results from previous tasks, improving responsiveness and reliability. Introduced beaconDispatchTasks to manage background execution and result collection.
2026-01-04 07:57:26 +05:30
Joe 30a50717e1 Merge pull request #2029 from BishopFox/7a58dependabot/go_modules/github.com/klauspost/compress-1.18.1
Bump github.com/klauspost/compress from 1.18.0 to 1.18.1
2026-01-02 21:34:03 -07:00
Joe f4c9e7109b Merge pull request #2084 from BishopFox/fix/mods2
Improvements to Implant Build Layout
2026-01-02 21:19:16 -07:00
moloch-- 08f3997756 Fix sliver.h -> main.h 2026-01-02 21:40:45 -06:00
moloch-- 3aebebecbb Implement RenameImport() 2026-01-02 21:22:19 -06:00
moloch-- 1d7eca8e39 Validate module path 2026-01-02 21:06:14 -06:00
moloch-- 773ee5f867 Rename sliver.go -> main.go 2026-01-02 20:55:26 -06:00
moloch-- 7608a4b0a0 Add GoPackage to ImplantConfig pb 2026-01-02 20:41:35 -06:00
moloch-- 9ecd36b7b6 Implement module rename 2026-01-02 20:33:27 -06:00
moloch-- d8fc5a6df4 Tweak project dir structure 2026-01-02 19:02:07 -06:00
moloch-- 649a6582e0 Add -mod=vendor to GoBuild 2026-01-02 18:53:31 -06:00
moloch-- 00e8a0cfca Move main implant code out of main pkg 2026-01-02 18:50:26 -06:00
moloch-- e9890bc8f9 Set limit to unauthed body req size 2026-01-02 17:32:43 -06:00
Joe 8b798f8307 Merge pull request #2083 from BishopFox/feature/mcp
Experiamental MCP Support
2026-01-02 16:25:24 -07:00
moloch-- d4728af1ea Add mcp logging 2026-01-02 16:26:01 -06:00
moloch-- d1b7d91b1a Add basic file operations to mcp 2026-01-02 12:48:35 -06:00
moloch-- cac6dced34 Tweak mcp 2026-01-02 11:14:32 -06:00
moloch-- a35bf0c1fc Implement mcp stdio option 2026-01-02 07:01:55 -06:00
moloch-- 67616e50c1 Wire rpc to mcp 2026-01-01 22:43:52 -06:00
moloch-- 54de8a2a97 Outline mcp 2026-01-01 22:32:25 -06:00
moloch-- 20916c655c Improve kick-operator command 2026-01-01 11:17:07 -06:00
Joe 6c18201ed6 Merge pull request #2081 from BishopFox/fix/rpc-errors
Improve gRPC Error Codes
2026-01-01 09:57:00 -07:00
moloch-- 29165aff0d Add /artifacts to gitignore 2026-01-01 10:04:53 -06:00
moloch-- 704285f28d Fix release workflow 2026-01-01 09:50:59 -06:00
moloch-- a007872fbc Include version history in release action 2026-01-01 09:48:56 -06:00
dependabot[bot] c09c57da3f Bump github.com/klauspost/compress from 1.18.0 to 1.18.1
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml)
- [Commits](https://github.com/klauspost/compress/compare/v1.18.0...v1.18.1)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-11-03 20:05:33 +00:00
Carter Burn a21b0da928 Merge branch 'BishopFox:master' into feature/aka-command 2025-10-28 21:16:15 -04:00
carterburn 8a0ba038f6 Add readme to give aka command breakdown 2025-10-27 21:29:35 -04:00
carterburn dd072bbdb6 Merge remote-tracking branch 'origin/master' into feature/aka-command 2025-10-26 21:48:59 -04:00
carterburn 1e3c736acf Update types to support storing aka aliases
Updated the way aka aliases are stored to support saving the aka aliases
to disk.

Aka aliases will automatically be saved to disk and loaded on client
startup.
2025-10-26 21:45:03 -04:00
carterburn 3d880914d9 Clean up settings and hook for no quotes
Previous commit forced the user to use quotes in the creation command
and in any arguments that include spaces when expanded a command alias.

This commit removes that requirement and allows the user to manipulate
the shell interface as if they were typing commands directly.
2025-10-23 20:39:19 -04:00
carterburn 1162de9ee3 Initial design of command aliases
Command aliases will allow a user to define "aliases" for shorthands and
to pass default arguments

Currently, aliases with more than one word commands (like a command
alias for "execute -o") require the use of single or double quotes to
pass the arguments correctly. Additionally, any use of flags in the
actual command will attempt to parse for the "aka" command and we want
the "aka create" command to be bare to just grab everything after the
command alias name.

Subsequent changes will include:
- better parsing of flags as described above
- saving command aliases to disk
- better help messages
2025-10-23 06:05:01 -04:00
770 changed files with 64127 additions and 23729 deletions

No files matched your search

+2
View File
@@ -38,6 +38,8 @@ jobs:
steps:
- name: Check Out Code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Go 1.25
uses: actions/setup-go@v6
+5
View File
@@ -2,6 +2,11 @@
/.devcontainer
/.idea
/artifacts
/artifacts/**
*.rc
/Dockerfile.*
/*.sdk.tar.xz
+3
View File
@@ -29,11 +29,14 @@ MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 24
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
LDFLAGS = -ldflags "-s -w \
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
+1 -4
View File
@@ -39,11 +39,8 @@ Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](h
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### Feedback
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
+12
View File
@@ -73,6 +73,18 @@ func GetConsoleLogsDir() string {
return consoleLogsDir
}
// GetMCPLogsDir - Get the Sliver client MCP logs dir ~/.sliver-client/logs/mcp/
func GetMCPLogsDir() string {
mcpLogsDir := filepath.Join(GetClientLogsDir(), "mcp")
if _, err := os.Stat(mcpLogsDir); os.IsNotExist(err) {
err = os.MkdirAll(mcpLogsDir, 0700)
if err != nil {
log.Fatal(err)
}
}
return mcpLogsDir
}
func assetVersion() string {
appDir := GetRootAppDir()
data, err := os.ReadFile(filepath.Join(appDir, versionFileName))
+4
View File
@@ -51,6 +51,7 @@ func init() {
defer logFile.Close()
rootCmd.TraverseChildren = true
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
// Create the console client, without any RPC or commands bound to it yet.
// This created before anything so that multiple commands can make use of
@@ -69,6 +70,9 @@ func init() {
// other command trees below, such as the implant one.
rootCmd.AddCommand(consoleCmd(con))
// MCP stdio server.
rootCmd.AddCommand(mcpCmd(con))
// Implant.
// The implant command allows users to run commands on slivers from their
// system shell. It makes use of pre-runners for connecting to the server
+9 -3
View File
@@ -40,6 +40,7 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
Short: "Start the sliver client console",
}
consoleCmd.Flags().String(RCFlagName, "", "path to rc script file")
consoleCmd.RunE, consoleCmd.PersistentPostRunE = consoleRunnerCmd(con, true)
return consoleCmd
}
@@ -47,7 +48,7 @@ func consoleCmd(con *console.SliverClient) *cobra.Command {
func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *cobra.Command, args []string) error) {
var ln *grpc.ClientConn
pre = func(_ *cobra.Command, _ []string) error {
pre = func(cmd *cobra.Command, _ []string) error {
configs := assets.GetConfigs()
if len(configs) == 0 {
@@ -59,13 +60,18 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
return nil
}
rcScript, err := ReadRCScript(cmd)
if err != nil {
fmt.Printf("Failed to read rc script: %s\n", err)
return nil
}
// Don't clobber output when simply running an implant command from system shell.
if run {
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
}
var rpc rpcpb.SliverRPCClient
var err error
rpc, ln, err = transport.MTLSConnect(config)
if err != nil {
@@ -76,7 +82,7 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
// Wait for any connection state changes and exit if the connection is lost.
go handleConnectionLost(ln)
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
}
// Close the RPC connection once exiting
+94
View File
@@ -0,0 +1,94 @@
package cli
/*
Sliver Implant Framework
Copyright (C) 2023 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/bishopfox/sliver/client/transport"
"github.com/spf13/cobra"
)
func mcpCmd(con *console.SliverClient) *cobra.Command {
mcpCmd := &cobra.Command{
Use: "mcp",
Short: "Start the MCP stdio server",
RunE: func(cmd *cobra.Command, args []string) error {
return runMCP(cmd, con)
},
}
mcpCmd.Flags().String("config", "", "path to client config file")
return mcpCmd
}
func runMCP(cmd *cobra.Command, con *console.SliverClient) error {
configPath, _ := cmd.Flags().GetString("config")
config, err := loadMCPClientConfig(configPath)
if err != nil {
fmt.Printf("%s\n", err)
return nil
}
rpc, ln, err := transport.MTLSConnect(config)
if err != nil {
fmt.Printf("Connection to server failed %s\n", err)
return nil
}
defer ln.Close()
con.Rpc = rpc
go handleConnectionLost(ln)
cfg := clientmcp.DefaultConfig()
if err := clientmcp.ServeStdio(cfg, con.Rpc); err != nil {
fmt.Printf("MCP server error: %s\n", err)
return nil
}
return nil
}
func loadMCPClientConfig(path string) (*assets.ClientConfig, error) {
if path != "" {
configPath := path
if !filepath.IsAbs(configPath) {
if _, err := os.Stat(configPath); err != nil {
configPath = filepath.Join(assets.GetConfigDir(), configPath)
}
}
return assets.ReadConfig(configPath)
}
configs := assets.GetConfigs()
switch len(configs) {
case 0:
return nil, fmt.Errorf("no config files found at %s (use --config)", assets.GetConfigDir())
case 1:
for _, config := range configs {
return config, nil
}
default:
return nil, fmt.Errorf("multiple configs found; use --config to select one")
}
return nil, fmt.Errorf("no config files found")
}
+31
View File
@@ -0,0 +1,31 @@
package cli
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
const RCFlagName = "rc"
func ReadRCScript(cmd *cobra.Command) (string, error) {
if cmd == nil {
return "", nil
}
if cmd.Flags().Lookup(RCFlagName) == nil {
return "", nil
}
rcPath, err := cmd.Flags().GetString(RCFlagName)
if err != nil {
return "", err
}
if rcPath == "" {
return "", nil
}
data, err := os.ReadFile(rcPath)
if err != nil {
return "", fmt.Errorf("read rc script %q: %w", rcPath, err)
}
return string(data), nil
}
+2 -1
View File
@@ -8,7 +8,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `command.go` – Provides helpers for binding Cobra commands, flag sets, and visibility filters into the client command tree.
- `server.go` – Defines the server menu composition, wiring each server-side command group onto the root console structure at runtime.
- `sliver.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
- `main.go` – Builds the implant/Sliver menu, attaching per-target command groups and loading alias/extension manifests before exposure.
## Sub-packages
@@ -40,6 +40,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
- `licenses/` – Implements the 'licenses' command group for the Sliver client console.
- `loot/` – Implements the 'loot' command group for the Sliver client console.
- `monitor/` – Implements the 'monitor' command group for the Sliver client console.
- `mcp/` – Implements the 'mcp' command group for the Sliver client console.
- `network/` – Implements the 'network' command group for the Sliver client console.
- `operators/` – Implements the 'operators' command group for the Sliver client console.
- `pivots/` – Implements the 'pivots' command group for the Sliver client console.
+23
View File
@@ -0,0 +1,23 @@
# client/command/aka
## Overview
Implements the 'aka' command group for the Sliver client console. "Aka"s are
command aliases (like 'alias' in 'bash') that allow operators to specify
shorthand commands and default arguments for commonly used commands.
### Examples:
- `aka create bg background`: Allows an operator to run `bg` from the implant
menu to quickly move back to the server menu
- `aka create execw execute -o "C:\Windows\System32\cmd.exe" /c`: Shorthand for
executing binaries on Windows targets like: `execw arp -a`
## Go Files
- `commands.go` – Wires Cobra commands for listing, creating, and deleting command aliases in the client console.
- `create.go` – Handles creating command aliases to use by the client.
- `delete.go` – Removes command aliases currently tracked by the client.
- `hook.go` – The main hooking logic to intercept commands ran by the client console. The hook will intercept processing before hitting the Cobra tree to transform the command alias to a full command with default arguments from the command alias and user-provided arguments to the alias.
- `list.go` - Lists the current command aliases tracked by the client.
- `storage.go` - Supports the saving of aliases to disk to keep between runs of the client.
+59
View File
@@ -0,0 +1,59 @@
package aka
import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
func ServerCommands(con *console.SliverClient) []*cobra.Command {
return commands(con, constants.GenericHelpGroup)
}
func ImplantCommands(con *console.SliverClient) []*cobra.Command {
return commands(con, constants.SliverCoreHelpGroup)
}
func commands(con *console.SliverClient, group string) []*cobra.Command {
akaCommand := &cobra.Command{
Use: "aka",
Short: "Manage command aliases (aka's)",
Run: func(cmd *cobra.Command, args []string) {
AkaListCmd(cmd, con, args)
},
GroupID: group,
}
createAka := &cobra.Command{
Use: "create <alias> <command> [args...]",
Short: "Create a new command alias (aka)",
Args: cobra.MinimumNArgs(2),
DisableFlagParsing: true, // No flags with aka create so we capture flags to other commands
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {
AkaCreateCmd(cmd, con, args)
},
}
createAka.InitDefaultHelpFlag()
createAka.Flags().Lookup("help").Hidden = true
akaCommand.AddCommand(createAka)
deleteAka := &cobra.Command{
Use: "delete <alias>",
Short: "Delete a command alias (aka)",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
AkaDeleteCmd(cmd, con, args)
},
}
akaCommand.AddCommand(deleteAka)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, Cmdhook)
err := LoadAkaAliases()
if err != nil {
con.PrintErrorf("Failed to load aka alises: %s\n", err)
}
return []*cobra.Command{akaCommand}
}
+46
View File
@@ -0,0 +1,46 @@
package aka
import (
"fmt"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
func AkaCreateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
aliasName := args[0]
command := args[1]
defaultArgs := args[2:] // anything else is going to be the default args to the alias
if _, exists := akaAliases[aliasName]; exists {
con.PrintErrorf("Alias '%s' already exists\n", aliasName)
return
}
var desc string
if len(defaultArgs) == 0 {
desc = fmt.Sprintf("%s", command)
} else {
desc = fmt.Sprintf("%s %s", command, strings.Join(defaultArgs, " "))
}
alias := &AkaAlias{
Name: aliasName,
Command: command,
DefaultArgs: defaultArgs,
Description: desc,
}
akaAliases[aliasName] = alias
// save this new alias to disk
err := SaveAkaAliases()
if err != nil {
con.PrintErrorf("Failed to save alias '%s' to disk: %s\n", aliasName, err)
// we still technically added it to the map, so the alias did get created in
// memory so we should still print the Info message below
}
con.PrintInfof("Create alias for '%s' -> '%v'\n", aliasName, alias.Description)
}
+25
View File
@@ -0,0 +1,25 @@
package aka
import (
"github.com/bishopfox/sliver/client/console"
"github.com/spf13/cobra"
)
func AkaDeleteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
aliasName := args[0]
if _, exists := akaAliases[aliasName]; !exists {
con.PrintErrorf("Alias '%s' does not exist\n", aliasName)
return
}
delete(akaAliases, aliasName)
// Save updated map to disk
err := SaveAkaAliases()
if err != nil {
con.PrintErrorf("Failed to save aliases to disk: %s\n", err)
return
}
con.PrintInfof("Deleted alias '%s'\n", aliasName)
}
+27
View File
@@ -0,0 +1,27 @@
package aka
import (
"strings"
)
func Cmdhook(args []string) ([]string, error) {
if len(args) == 0 {
return args, nil
}
if aliasArgs, exists := akaAliases[args[0]]; exists {
var expandedArgs []string
expandedArgs = append(expandedArgs, aliasArgs.Command)
expandedArgs = append(expandedArgs, aliasArgs.DefaultArgs...)
// join the user passed args so the shell processes it without flags if
// available
if len(args[1:]) > 0 {
expandedArgs = append(expandedArgs, strings.Join(args[1:], " "))
}
return expandedArgs, nil
}
return args, nil
}
+31
View File
@@ -0,0 +1,31 @@
package aka
import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
func AkaListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if len(akaAliases) == 0 {
con.PrintInfof("No command aliases created. Use `aka create` to create one.\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Alias",
"Command",
})
for alias, cmd := range akaAliases {
tw.AppendRow(table.Row{
alias,
cmd.Description,
})
}
con.Printf("%s\n", tw.Render())
}
+68
View File
@@ -0,0 +1,68 @@
package aka
import (
"encoding/json"
"os"
"path/filepath"
"github.com/bishopfox/sliver/client/assets"
)
type AkaAlias struct {
Name string `json:"name"` // alias Name
Command string `json:"command"` // command being aliased
DefaultArgs []string `json:"default_args"` // args passed by the user
Description string `json:"description"`
}
var akaAliases = make(map[string]*AkaAlias)
const (
akaAliasFileName = "aka-aliases.json"
)
func GetAkaAliasesFilePath() string {
return filepath.Join(assets.GetRootAppDir(), akaAliasFileName)
}
func LoadAkaAliases() error {
filepath := GetAkaAliasesFilePath()
data, err := os.ReadFile(filepath)
if err != nil {
// if file hasn't been created yet, we fail gracefully
if os.IsNotExist(err) {
return nil
}
// otherwise, return the actual error; something else went wrong
return err
}
aliases := []AkaAlias{}
err = json.Unmarshal(data, &aliases)
if err != nil {
return err
}
akaAliases = make(map[string]*AkaAlias)
for _, alias := range aliases {
akaAliases[alias.Name] = &alias
}
return nil
}
func SaveAkaAliases() error {
filepath := GetAkaAliasesFilePath()
aliases := make([]*AkaAlias, 0, len(akaAliases))
for _, alias := range akaAliases {
aliases = append(aliases, alias)
}
data, err := json.MarshalIndent(aliases, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath, data, 0o600)
}
+168 -5
View File
@@ -36,6 +36,7 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
app "github.com/reeflective/console"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -76,6 +77,16 @@ type AliasFile struct {
Path string `json:"path"`
}
// AliasArgument - An argument for an alias command.
type AliasArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
// AliasManifest - The manifest for an alias, contains metadata.
type AliasManifest struct {
Name string `json:"name"`
@@ -89,6 +100,7 @@ type AliasManifest struct {
Entrypoint string `json:"entrypoint"`
AllowArgs bool `json:"allow_args"`
DefaultArgs string `json:"default_args"`
Arguments []*AliasArgument `json:"arguments"`
Files []*AliasFile `json:"files"`
IsReflective bool `json:"is_reflective"`
IsAssembly bool `json:"is_assembly"`
@@ -158,15 +170,60 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
}
aliasManifest.RootPath = filepath.Dir(manifestPath)
// Build usage string including arguments
usage := strings.Builder{}
usage.WriteString(aliasManifest.CommandName)
for _, arg := range aliasManifest.Arguments {
usage.WriteString(" ")
if arg.Optional {
usage.WriteString("[")
}
usage.WriteString(strings.ToUpper(arg.Name))
if arg.Optional {
usage.WriteString("]")
}
}
// Build long help message
longHelp := strings.Builder{}
longHelp.WriteString("[[.Bold]]Command:[[.Normal]]")
longHelp.WriteString(usage.String())
longHelp.WriteString("\n")
if len(aliasManifest.Help) > 0 || len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString("[[.Bold]]About:[[.Normal]]")
if len(aliasManifest.Help) > 0 {
longHelp.WriteString(aliasManifest.Help)
longHelp.WriteString("\n")
}
if len(aliasManifest.LongHelp) > 0 {
longHelp.WriteString(aliasManifest.LongHelp)
longHelp.WriteString("\n")
}
}
if len(aliasManifest.Arguments) > 0 {
longHelp.WriteString("[[.Bold]]Arguments:[[.Normal]]")
for _, arg := range aliasManifest.Arguments {
longHelp.WriteString("\n\t")
optStr := ""
if arg.Optional {
optStr = "[OPTIONAL]"
}
aType := arg.Type
if aType == "wstring" {
aType = "string"
}
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
longHelpMsg := help.FormatHelpTmpl(aliasManifest.LongHelp)
longHelpMsg += "\n\n⚠️ If you're having issues passing arguments to the alias please read:\n"
longHelpMsg += "https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing"
addAliasCmd := &cobra.Command{
Use: aliasManifest.CommandName,
Use: usage.String(),
Short: helpMsg,
Long: longHelpMsg,
Long: help.FormatHelpTmpl(longHelp.String()),
Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args)
},
@@ -196,6 +253,10 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
f.IntP("timeout", "t", defaultTimeout, "command timeout in seconds")
addAliasCmd.Flags().AddFlagSet(f)
// Setup completions for alias arguments
comps := carapace.Gen(addAliasCmd)
makeAliasArgCompleter(aliasManifest, comps)
cmd.AddCommand(addAliasCmd)
// Have to use a global map here, as passing the aliasCmd
@@ -589,3 +650,105 @@ func makeAliasPlatformFilters(alias *AliasManifest) map[string]string {
app.CommandFilterKey: strings.Join(all, ","),
}
}
// makeAliasArgCompleter builds the positional and dash arguments completer for the alias.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --target, --port)
func makeAliasArgCompleter(alias *AliasManifest, comps *carapace.Carapace) {
if len(alias.Arguments) == 0 {
return
}
var actions []carapace.Action
for _, arg := range alias.Arguments {
var action carapace.Action
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("alias data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
if arg.Optional {
usage += " (optional)"
}
actions = append(actions, action.Usage("%s", usage))
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// Pre-build the flag completions at registration time (not in a callback)
flagCompletion := makeAliasFlagNameCompletion(alias)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range alias.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
case "bool":
valueCompletions[arg.Name] = carapace.ActionValues("true", "false").Tag("boolean")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
// makeAliasFlagNameCompletion creates completion for flag names
func makeAliasFlagNameCompletion(alias *AliasManifest) carapace.Action {
var results []string
for _, arg := range alias.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("alias arguments")
}
+16 -4
View File
@@ -274,19 +274,22 @@ func getInstalledPackageNames() []string {
}
// This is a convenience function to get the names of the commands in the cache
func getCommandsInCache() []string {
func getCommandsInCache(armoryPK string) []string {
commandNames := []string{}
pkgCache.Range(func(key, value interface{}) bool {
cacheEntry := value.(pkgCacheEntry)
if cacheEntry.LastErr == nil {
if armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey != armoryPK {
return true
}
if cacheEntry.Pkg.IsAlias {
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
if cacheEntry.Alias.CommandName != "" && !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
}
} else {
for _, command := range cacheEntry.Extension.ExtCommand {
if !slices.Contains(commandNames, command.CommandName) {
if command.CommandName != "" && !slices.Contains(commandNames, command.CommandName) {
commandNames = append(commandNames, command.CommandName)
}
}
@@ -398,7 +401,7 @@ func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPack
var requestedPackageList []string
if name == "all" {
requestedPackageList = []string{}
allCommands := getCommandsInCache()
allCommands := getCommandsInCache(armoryPK)
for _, cmdName := range allCommands {
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
// Check to see if there is a package pending with that name
@@ -458,6 +461,15 @@ func installPackageByName(name, armoryPK string, forceInstallation, promptToOver
if err != nil {
return err
}
if len(packageInstallList) == 0 && name == "all" {
availableCommands := getCommandsInCache(armoryPK)
if len(availableCommands) == 0 {
con.PrintInfof("No packages or bundles found\n")
} else {
con.PrintInfof("All available packages are already installed\n")
}
return nil
}
if len(packageInstallList) > 0 {
for _, packageID := range packageInstallList {
entry := packageCacheLookupByID(packageID)
+13
View File
@@ -39,6 +39,7 @@ import (
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
killFlag, _ := cmd.Flags().GetString("kill")
killAll, _ := cmd.Flags().GetBool("kill-all")
interact, _ := cmd.Flags().GetString("interact")
// Handle kill
if killFlag != "" {
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
}
}
if interact != "" {
beacon, err := GetBeacon(con, interact)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.ActiveTarget.Set(nil, beacon)
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
return
}
filter, _ := cmd.Flags().GetString("filter")
var filterRegex *regexp.Regexp
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
+70 -15
View File
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
f.StringP("kill", "k", "", "kill the designated beacon")
f.BoolP("kill-all", "K", false, "kill all beacons")
f.StringP("interact", "i", "", "interact with a beacon")
f.BoolP("force", "F", false, "force killing the beacon")
f.StringP("filter", "f", "", "filter beacons by substring")
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
(*comp)["kill"] = BeaconIDCompleter(con)
(*comp)["interact"] = BeaconIDCompleter(con)
})
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
beaconsRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove a beacon",
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
// BeaconIDCompleter completes beacon IDs
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err == nil {
for _, b := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, b.ID[:8])
results = append(results, desc)
}
}
return carapace.ActionValuesDescribed(results...).Tag("beacons")
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
}
return carapace.ActionCallback(callback)
}
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values := describedValuesToTabs(beaconCompletionPairs(con))
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func beaconCompletionPairs(con *console.SliverClient) []string {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return results
}
for _, b := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, b.ID[:8], desc)
}
return results
}
func describedValuesToTabs(values []string) []string {
tabbed := make([]string, 0, len(values)/2)
for i := 0; i+1 < len(values); i += 2 {
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
}
return tabbed
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
+21 -4
View File
@@ -21,6 +21,7 @@ package c2profiles
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
@@ -42,6 +43,10 @@ import (
"github.com/bishopfox/sliver/protobuf/commonpb"
)
var (
ErrNoSelection = errors.New("no selection")
)
// C2ProfileCmd list available http profiles
func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
profileName, _ := cmd.Flags().GetString("name")
@@ -53,7 +58,11 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
return
}
if len(httpC2Profiles.Configs) != 1 {
profileName = selectC2Profile(httpC2Profiles.Configs)
profileName, err = selectC2Profile(httpC2Profiles.Configs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
}
@@ -124,7 +133,11 @@ func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
return
}
if len(httpC2Profiles.Configs) != 1 {
profileName = selectC2Profile(httpC2Profiles.Configs)
profileName, err = selectC2Profile(httpC2Profiles.Configs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
}
@@ -540,7 +553,7 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
con.Println("\n")
}
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) (string, error) {
c2profile := ""
var choices []string
for _, c2profile := range c2profiles {
@@ -549,7 +562,11 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
_ = forms.Select("Select a c2 profile", choices, &c2profile)
return c2profile
if c2profile == "" {
return "", ErrNoSelection
}
return c2profile, nil
}
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
+3 -1
View File
@@ -25,12 +25,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
f.BoolP("output", "o", false, "capture command output")
f.BoolP("output", "o", true, "capture command output")
f.BoolP("save", "s", false, "save output to a file")
f.BoolP("loot", "X", false, "save output as loot")
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
f.Bool("env-inheritance", true, "inherit environment variables from the current process")
f.StringArray("env", nil, "set an environment variable for the child process (key=value)")
f.StringP("name", "n", "", "name to assign loot (optional)")
f.Uint32P("ppid", "P", 0, "parent process id (optional, Windows only)")
f.BoolP("hidden", "H", false, "hide the window of the spawned process (Windows only)")
+35 -7
View File
@@ -49,6 +49,13 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
saveLoot, _ := cmd.Flags().GetBool("loot")
saveOutput, _ := cmd.Flags().GetBool("save")
ppid, _ := cmd.Flags().GetUint32("ppid")
envInheritance, _ := cmd.Flags().GetBool("env-inheritance")
envPairs, _ := cmd.Flags().GetStringArray("env")
envVars, err := parseEnvPairs(envPairs)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
hostName := getHostname(session, beacon)
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
@@ -59,15 +66,19 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
var exec *sliverpb.Execute
var err error
ctrl := make(chan bool)
con.PrintInfof("Execute: %s [%s]\n", cmdPath, strings.Join(args, " "))
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
if token || hidden || ppid != 0 {
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
return
}
if envInheritance || len(envVars) > 0 {
con.PrintErrorf("The env and env-inheritance options are not supported with token, hidden, or ppid\n")
return
}
exec, err = con.Rpc.ExecuteWindows(context.Background(), &sliverpb.ExecuteWindowsReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
@@ -81,12 +92,14 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
})
} else {
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
Request: con.ActiveTarget.Request(cmd),
Path: cmdPath,
Args: args,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
EnvInheritance: envInheritance,
Env: envVars,
})
}
ctrl <- true
@@ -179,6 +192,21 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
return ""
}
func parseEnvPairs(envPairs []string) (map[string]string, error) {
if len(envPairs) == 0 {
return nil, nil
}
envVars := make(map[string]string, len(envPairs))
for _, pair := range envPairs {
parts := strings.SplitN(pair, "=", 2)
if len(parts) != 2 || parts[0] == "" {
return nil, fmt.Errorf("invalid --env value %q, expected key=value", pair)
}
envVars[parts[0]] = parts[1]
}
return envVars, nil
}
func determineCommandName(command string) string {
commandName := strings.ReplaceAll(command, "\\", "/")
+13 -6
View File
@@ -20,7 +20,6 @@ package exit
import (
"context"
"fmt"
"os"
"github.com/bishopfox/sliver/client/console"
@@ -32,26 +31,25 @@ import (
// ExitCmd - Exit the console.
func ExitCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
fmt.Println("Exiting...")
if con.IsServer {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
os.Exit(1)
flushAndExit(con, 1)
}
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
os.Exit(1)
flushAndExit(con, 1)
}
if 0 < len(sessions.Sessions) || 0 < len(beacons.Beacons) {
con.Printf("There are %d active sessions and %d active beacons.\n", len(sessions.Sessions), len(beacons.Beacons))
confirm := false
_ = forms.Confirm("Are you sure you want to exit?", &confirm)
forms.Confirm("Are you sure you want to exit?", &confirm)
if !confirm {
return
}
}
}
os.Exit(0)
flushAndExit(con, 0)
}
// Commands returns the `exit` command.
@@ -65,3 +63,12 @@ func Command(con *console.SliverClient) []*cobra.Command {
GroupID: constants.GenericHelpGroup,
}}
}
func flushAndExit(con *console.SliverClient, code int) {
if con != nil {
con.FlushOutput()
} else {
os.Stdout.Sync()
}
os.Exit(code)
}
+83 -94
View File
@@ -30,7 +30,6 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/bishopfox/sliver/client/assets"
@@ -43,8 +42,8 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/carapace-sh/carapace"
appConsole "github.com/reeflective/console"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/protobuf/proto"
@@ -121,6 +120,7 @@ type extensionArgument struct {
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
Choices []string `json:"choices,omitempty"`
}
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
@@ -689,94 +689,6 @@ func PrintExtOutput(extName string, commandName string, outputSchema *packages.O
}
}
func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
var err error
argsBuffer := core.BOFArgsBuffer{
Buffer: new(bytes.Buffer),
}
// Parse BOF arguments from grumble
missingRequiredArgs := make([]string, 0)
// If we have an extension that expects a single string, but more than one has been parsed, combine them
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
// The loop below will only read the first element of args because ext.Arguments is 1
args[0] = strings.Join(args, " ")
}
for _, arg := range ext.Arguments {
// If we don't have any positional words left to consume,
// add the remaining required extension arguments in the
// error message.
if len(args) == 0 {
if !arg.Optional {
missingRequiredArgs = append(missingRequiredArgs, "`"+arg.Name+"`")
}
continue
}
// Else pop a word from the list
word := args[0]
args = args[1:]
switch arg.Type {
case "integer":
fallthrough
case "int":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddInt(uint32(val))
if err != nil {
return nil, err
}
case "short":
val, err := strconv.Atoi(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddShort(uint16(val))
if err != nil {
return nil, err
}
case "string":
err = argsBuffer.AddString(word)
if err != nil {
return nil, err
}
case "wstring":
err = argsBuffer.AddWString(word)
if err != nil {
return nil, err
}
// Adding support for filepaths so we can
// send binary data like shellcodes to BOFs
case "file":
data, err := os.ReadFile(word)
if err != nil {
return nil, err
}
err = argsBuffer.AddData(data)
if err != nil {
return nil, err
}
}
}
// Return if we have missing required arguments
if len(missingRequiredArgs) > 0 {
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
}
parsedArgs, err := argsBuffer.GetBuffer()
if err != nil {
return nil, err
}
return parsedArgs, nil
}
func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtCommand) ([]byte, error) {
var extensionArgs []byte
binData, err := os.ReadFile(binPath)
@@ -837,16 +749,27 @@ func checkExtensionArgs(extCmd *ExtCommand) error {
return nil
}
// makeExtensionArgCompleter builds the positional arguments completer for the extension.
// makeExtensionArgCompleter builds the positional and dash arguments completer for the extension.
// It provides completion for:
// 1. Positional arguments (before --)
// 2. Flag-style arguments after -- (e.g., --process, --shellcode)
func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *carapace.Carapace) {
var actions []carapace.Action
for _, arg := range extCmd.Arguments {
var action carapace.Action
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
action = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
action = carapace.ActionFiles().Tag("extension data")
default:
action = carapace.ActionValues()
}
}
usage := fmt.Sprintf("(%s) %s", arg.Type, arg.Desc)
@@ -858,6 +781,72 @@ func makeExtensionArgCompleter(extCmd *ExtCommand, _ *cobra.Command, comps *cara
}
comps.PositionalCompletion(actions...)
// Add dash completion for flag-style arguments after --
// The extension arguments are parsed as flags (--name value) by argparser.go
if len(extCmd.Arguments) > 0 {
// Pre-build the flag completions at registration time (not in a callback)
// This ensures the values are captured correctly
flagCompletion := makeExtensionFlagNameCompletion(extCmd)
// Build value completions for each argument type
valueCompletions := make(map[string]carapace.Action)
for _, arg := range extCmd.Arguments {
// If choices are defined, use them for completion
if len(arg.Choices) > 0 {
valueCompletions[arg.Name] = carapace.ActionValues(arg.Choices...).Tag("choices")
} else {
// Fall back to type-based completion
switch arg.Type {
case "file":
valueCompletions[arg.Name] = carapace.ActionFiles().Tag("file path")
default:
valueCompletions[arg.Name] = carapace.ActionValues()
}
}
}
// Use DashAnyCompletion with a smart action that determines context
comps.DashAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// If typing a flag (starts with -)
if strings.HasPrefix(c.Value, "-") {
return flagCompletion
}
// If previous arg was a flag, complete its value
if len(c.Args) > 0 {
lastArg := c.Args[len(c.Args)-1]
if strings.HasPrefix(lastArg, "-") {
flagName := strings.TrimLeft(lastArg, "-")
if action, ok := valueCompletions[flagName]; ok {
return action
}
}
}
// Default: show flag names
return flagCompletion
}),
)
}
}
// makeExtensionFlagNameCompletion creates completion for flag names
func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
var results []string
for _, arg := range extCmd.Arguments {
flagName := fmt.Sprintf("--%s", arg.Name)
desc := arg.Desc
if arg.Optional {
desc = fmt.Sprintf("[optional] %s", desc)
}
desc = fmt.Sprintf("(%s) %s", arg.Type, desc)
results = append(results, flagName, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("extension arguments")
}
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
+1 -1
View File
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Sliver Name",
"Implant Name",
"Domain",
"Triggered",
"First Trigger",
+1
View File
@@ -357,6 +357,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
(*comp)["c2profile"] = HTTPC2Completer(con)
})
registerImplantTargetFlagCompletions(cmd, con)
}
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
+2 -1
View File
@@ -22,7 +22,8 @@ var (
// GenerateBeaconCmd - The main command used to generate implant binaries
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateBeaconForm(cmd, con, args) {
result, err := forms.GenerateBeaconForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateBeaconForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
+12 -4
View File
@@ -85,12 +85,14 @@ var (
ErrNoExternalBuilder = errors.New("no external builders are available")
ErrNoValidBuilders = errors.New("no valid external builders for target")
ErrNoSelection = errors.New("no selection")
)
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
result, err := forms.GenerateForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
@@ -117,14 +119,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} else {
_, err := externalBuild(name, config, save, con)
if err != nil {
if err == ErrNoExternalBuilder {
switch err {
case ErrNoExternalBuilder:
con.PrintErrorf("There are no external builders currently connected to the server\n")
con.PrintErrorf("See 'builders' command for more information\n")
} else if err == ErrNoValidBuilders {
case ErrNoValidBuilders:
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
con.PrintErrorf("See 'builders' command for more information\n")
} else {
default:
con.PrintErrorf("%s\n", err)
}
return
@@ -1144,6 +1147,11 @@ func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient
}
choice := ""
_ = forms.Select("Select an external builder:", choices, &choice)
if choice == "" {
return nil, ErrNoSelection
}
for _, builder := range builders {
if builder.Name == choice {
return builder, nil
+91 -48
View File
@@ -3,11 +3,13 @@ package generate
import (
"context"
"fmt"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)
// GetSliverBinary - Get the binary of an implant based on it's profile.
@@ -51,71 +53,112 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
return data, err
}
func compilerTargets(con *console.SliverClient) (*clientpb.Compiler, error) {
if con == nil || con.Rpc == nil {
return nil, fmt.Errorf("no compiler info")
}
return con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
}
func appendUnique(values []string, value string) []string {
for _, existing := range values {
if existing == value {
return values
}
}
return append(values, value)
}
func compilerArchValues(compiler *clientpb.Compiler) []string {
results := []string{}
for _, target := range compiler.Targets {
results = appendUnique(results, target.GOARCH)
}
for _, target := range compiler.UnsupportedTargets {
results = appendUnique(results, target.GOARCH)
}
return results
}
func compilerOSValues(compiler *clientpb.Compiler) []string {
results := []string{}
for _, target := range compiler.Targets {
results = appendUnique(results, target.GOOS)
}
for _, target := range compiler.UnsupportedTargets {
results = appendUnique(results, target.GOOS)
}
return results
}
func filterByPrefix(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
if strings.HasPrefix(value, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
func registerTargetFlagCompletion(cmd *cobra.Command, name string, valuesFn func(*clientpb.Compiler) []string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
compiler, err := compilerTargets(con)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
values := valuesFn(compiler)
return filterByPrefix(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func registerImplantTargetFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
registerTargetFlagCompletion(cmd, "os", compilerOSValues, con)
registerTargetFlagCompletion(cmd, "arch", compilerArchValues, con)
}
// FormatCompleter completes builds' architectures.
func ArchCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
compiler, err := compilerTargets(con)
if err != nil {
return carapace.ActionMessage("No compiler info: %s", err.Error())
}
var results []string
nextTarget:
for _, target := range compiler.Targets {
for _, res := range results {
if res == target.GOARCH {
continue nextTarget
}
}
results = append(results, target.GOARCH)
}
nextUnsupported:
for _, target := range compiler.UnsupportedTargets {
for _, res := range results {
if res == target.GOARCH {
continue nextUnsupported
}
}
results = append(results, target.GOARCH)
}
return carapace.ActionValues(results...).Tag("architectures")
return carapace.ActionValues(compilerArchValues(compiler)...).Tag("architectures")
})
}
// FormatCompleter completes build operating systems.
func OSCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
compiler, err := compilerTargets(con)
if err != nil {
return carapace.ActionMessage("No compiler info: %s", err.Error())
}
var results []string
nextTarget:
for _, target := range compiler.Targets {
for _, res := range results {
if res == target.GOOS {
continue nextTarget
}
}
results = append(results, target.GOOS)
}
nextUnsupported:
for _, target := range compiler.UnsupportedTargets {
for _, res := range results {
if res == target.GOOS {
continue nextUnsupported
}
}
results = append(results, target.GOOS)
}
return carapace.ActionValues(results...).Tag("operating systems")
return carapace.ActionValues(compilerOSValues(compiler)...).Tag("operating systems")
})
}
+1 -1
View File
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
config.Format,
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf(config.HTTPC2ConfigName),
fmt.Sprintf("%s", config.HTTPC2ConfigName),
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
fmt.Sprintf("%v", builds.Staged[sliverName]),
})
+5 -1
View File
@@ -283,6 +283,10 @@ func SelectTrafficEncoder(con *console.SliverClient) string {
}
sort.Strings(encoderNames)
var selectedEncoder string
_ = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
err = forms.Select("Select a traffic encoder:", encoderNames, &selectedEncoder)
if err != nil {
con.PrintErrorf("%s", err)
return ""
}
return selectedEncoder
}
+17
View File
@@ -68,6 +68,7 @@ var (
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteStr: executeHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
@@ -124,6 +125,22 @@ var (
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
}
executeHelp = `[[.Bold]]Command:[[.Normal]] execute <path to executable> [options] -- [args...]
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
arguments are be separated from the sliver 'execute' arguments with '--'
[[.Bold]]Example:[[.Normal]]
execute /bin/bash --env FOO=1 -- -c env
[*] Execute: /bin/bash [-c env]
[*] Output:
FOO=1
PWD=/Users/moloch/git/sliver
SHLVL=1
_=/usr/bin/env
`
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
[[.Bold]]About:[[.Normal]] Manage jobs/listeners.`
+76
View File
@@ -0,0 +1,76 @@
package mcp
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the `mcp` command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
defaultConfig := clientmcp.DefaultConfig()
mcpCmd := &cobra.Command{
Use: consts.MCPStr,
Short: "Manage the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr}),
GroupID: consts.NetworkHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
McpCmd(cmd, con, args)
},
}
startCmd := &cobra.Command{
Use: consts.StartStr,
Short: "Start the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr, consts.StartStr}),
Run: func(cmd *cobra.Command, args []string) {
McpStartCmd(cmd, con, args)
},
}
flags.Bind("", false, startCmd, func(f *pflag.FlagSet) {
f.String("transport", string(defaultConfig.Transport), "mcp transport (http or sse)")
f.String("listen", defaultConfig.ListenAddress, "listen address for the MCP server")
f.String("name", defaultConfig.ServerName, "server name for MCP initialize")
f.String("version", defaultConfig.ServerVersion, "server version for MCP initialize")
})
flags.BindFlagCompletions(startCmd, func(comp *carapace.ActionMap) {
(*comp)["transport"] = carapace.ActionValues("http", "sse")
})
mcpCmd.AddCommand(startCmd)
stopCmd := &cobra.Command{
Use: consts.StopStr,
Short: "Stop the MCP server",
Long: help.GetHelpFor([]string{consts.MCPStr, consts.StopStr}),
Run: func(cmd *cobra.Command, args []string) {
McpStopCmd(cmd, con, args)
},
}
mcpCmd.AddCommand(stopCmd)
consoleCmd := &cobra.Command{
Use: "console",
Aliases: []string{"repl"},
Short: "Open an MCP console session",
Long: help.GetHelpFor([]string{consts.MCPStr, "console"}),
Run: func(cmd *cobra.Command, args []string) {
McpConsoleCmd(cmd, con, args)
},
}
flags.Bind("", false, consoleCmd, func(f *pflag.FlagSet) {
f.String("transport", string(defaultConfig.Transport), "mcp transport (http or sse)")
f.String("url", "", "mcp server URL (defaults to local server settings)")
})
flags.BindFlagCompletions(consoleCmd, func(comp *carapace.ActionMap) {
(*comp)["transport"] = carapace.ActionValues("http", "sse")
})
mcpCmd.AddCommand(consoleCmd)
return []*cobra.Command{mcpCmd}
}
+132
View File
@@ -0,0 +1,132 @@
package mcp
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/bishopfox/sliver/client/version"
mcpclient "github.com/mark3labs/mcp-go/client"
mcpapi "github.com/mark3labs/mcp-go/mcp"
"github.com/spf13/cobra"
)
// McpConsoleCmd connects to an MCP server and starts a minimal console.
func McpConsoleCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
rawTransport, _ := cmd.Flags().GetString("transport")
transport, err := clientmcp.ParseTransport(rawTransport)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
targetURL, _ := cmd.Flags().GetString("url")
if targetURL == "" {
cfg := clientmcp.GetStatus().Config.WithDefaults()
cfg.Transport = transport
targetURL, err = cfg.EndpointURL()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
client, err := newConsoleClient(transport, targetURL)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
defer client.Close()
ctx := context.Background()
if err := client.Start(ctx); err != nil {
con.PrintErrorf("%s\n", err)
return
}
_, err = client.Initialize(ctx, mcpapi.InitializeRequest{
Params: mcpapi.InitializeParams{
ProtocolVersion: mcpapi.LATEST_PROTOCOL_VERSION,
ClientInfo: mcpapi.Implementation{
Name: "sliver-mcp-console",
Version: version.Version,
},
Capabilities: mcpapi.ClientCapabilities{},
},
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Connected to MCP server (%s)\n", targetURL)
runConsoleREPL(ctx, client, cmd.InOrStdin(), cmd.OutOrStdout())
}
func newConsoleClient(transport clientmcp.Transport, targetURL string) (*mcpclient.Client, error) {
switch transport {
case clientmcp.TransportHTTP:
return mcpclient.NewStreamableHttpClient(targetURL)
case clientmcp.TransportSSE:
return mcpclient.NewSSEMCPClient(targetURL)
default:
return nil, fmt.Errorf("unsupported transport %q", transport)
}
}
func runConsoleREPL(ctx context.Context, client *mcpclient.Client, in io.Reader, out io.Writer) {
reader := bufio.NewReader(in)
for {
fmt.Fprint(out, "mcp> ")
line, err := reader.ReadString('\n')
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Fprintln(out)
return
}
fmt.Fprintf(out, "read error: %s\n", err)
return
}
line = strings.TrimSpace(line)
switch line {
case "":
continue
case "exit", "quit":
return
case "help", "?":
printConsoleHelp(out)
case "tools":
printConsoleTools(ctx, client, out)
default:
fmt.Fprintf(out, "unknown command: %s\n", line)
}
}
}
func printConsoleHelp(out io.Writer) {
fmt.Fprintln(out, "commands: tools, help, exit")
}
func printConsoleTools(ctx context.Context, client *mcpclient.Client, out io.Writer) {
tools, err := client.ListTools(ctx, mcpapi.ListToolsRequest{})
if err != nil {
fmt.Fprintf(out, "tools error: %s\n", err)
return
}
if len(tools.Tools) == 0 {
fmt.Fprintln(out, "no tools available")
return
}
for _, tool := range tools.Tools {
if tool.Description != "" {
fmt.Fprintf(out, "- %s: %s\n", tool.Name, tool.Description)
continue
}
fmt.Fprintf(out, "- %s\n", tool.Name)
}
}
+37
View File
@@ -0,0 +1,37 @@
package mcp
import (
"time"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
// McpCmd prints the current MCP server state.
func McpCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
status := clientmcp.GetStatus()
state := "stopped"
if status.Running {
state = "running"
}
con.Printf("Status: %s\n", state)
con.Printf("Transport: %s\n", status.Config.Transport)
con.Printf("Listen: %s\n", status.Config.ListenAddress)
endpoint, err := status.Config.EndpointURL()
if err == nil {
con.Printf("Endpoint: %s\n", endpoint)
} else {
con.PrintErrorf("Endpoint: %s\n", err)
}
if status.Running && !status.StartedAt.IsZero() {
uptime := time.Since(status.StartedAt).Truncate(time.Second)
con.Printf("Uptime: %s\n", uptime)
}
if status.LastError != "" {
con.PrintErrorf("Last error: %s\n", status.LastError)
}
}
+46
View File
@@ -0,0 +1,46 @@
package mcp
import (
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
slivermcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
// McpStartCmd starts the local MCP server.
func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
rawTransport, _ := cmd.Flags().GetString("transport")
transport, err := slivermcp.ParseTransport(rawTransport)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
listen, _ := cmd.Flags().GetString("listen")
name, _ := cmd.Flags().GetString("name")
version, _ := cmd.Flags().GetString("version")
cfg := slivermcp.Config{
Transport: transport,
ListenAddress: listen,
ServerName: name,
ServerVersion: version,
}.WithDefaults()
msg := `Do you know what prompt injection is and are you an adult?`
if !settings.IsUserAnAdultWithPrompt(con, msg) {
con.PrintErrorf("Failed to start MCP server, the user is not qualified to use feature\n")
return
}
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Starting MCP server (%s) on %s\n", cfg.Transport, cfg.ListenAddress)
endpoint, err := cfg.EndpointURL()
if err == nil {
con.PrintInfof("Endpoint: %s\n", endpoint)
}
}
+24
View File
@@ -0,0 +1,24 @@
package mcp
import (
"context"
"time"
"github.com/bishopfox/sliver/client/console"
clientmcp "github.com/bishopfox/sliver/client/mcp"
"github.com/spf13/cobra"
)
const mcpStopTimeout = 5 * time.Second
// McpStopCmd stops the local MCP server.
func McpStopCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), mcpStopTimeout)
defer cancel()
if err := clientmcp.Stop(ctx); err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("MCP server stopped\n")
}
+12
View File
@@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
@@ -44,6 +45,17 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
process, _ := cmd.Flags().GetString("process")
config := con.GetActiveSessionConfig()
/* If the HTTP C2 Config name is not defined, then put in the default value
This will have no effect on implants that do not use HTTP C2
Also this should be overridden when the build info is pulled from the
database, but if there is no build info and we have to create the build
from scratch, we need to have something in here.
*/
if config.HTTPC2ConfigName == "" {
config.HTTPC2ConfigName = consts.DefaultC2Profile
}
ctrl := make(chan bool)
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
+4
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"os"
"github.com/bishopfox/sliver/client/command/aka"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/armory"
"github.com/bishopfox/sliver/client/command/beacons"
@@ -39,6 +40,7 @@ import (
"github.com/bishopfox/sliver/client/command/jobs"
"github.com/bishopfox/sliver/client/command/licenses"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/command/mcp"
"github.com/bishopfox/sliver/client/command/monitor"
"github.com/bishopfox/sliver/client/command/operators"
"github.com/bishopfox/sliver/client/command/reaction"
@@ -101,11 +103,13 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
crack.Commands,
certificates.Commands,
clean.Command,
aka.ServerCommands,
)
// C2 Network
bind(consts.NetworkHelpGroup,
jobs.Commands,
mcp.Commands,
websites.Commands,
wireguard.Commands,
c2profiles.Commands,
+68 -15
View File
@@ -43,6 +43,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
(*comp)["interact"] = SessionIDCompleter(con)
(*comp)["kill"] = SessionIDCompleter(con)
})
registerSessionIDFlagCompletion(sessionsCmd, "interact", con)
sessionsPruneCmd := &cobra.Command{
Use: consts.PruneStr,
@@ -63,26 +64,78 @@ func Commands(con *console.SliverClient) []*cobra.Command {
// SessionIDCompleter completes session IDs.
func SessionIDCompleter(con *console.SliverClient) carapace.Action {
callback := func(_ carapace.Context) carapace.Action {
results := make([]string, 0)
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err == nil {
for _, s := range sessions.Sessions {
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, s.ID[:8])
results = append(results, desc)
}
}
return carapace.ActionValuesDescribed(results...).Tag("sessions")
return carapace.ActionValuesDescribed(sessionCompletionPairs(con)...).Tag("sessions")
}
return carapace.ActionCallback(callback)
}
func registerSessionIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
if cmd == nil {
return
}
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
return
}
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
return
}
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
values := describedValuesToTabs(sessionCompletionPairs(con))
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
})
}
func sessionCompletionPairs(con *console.SliverClient) []string {
results := make([]string, 0)
if con == nil || con.Rpc == nil {
return results
}
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return results
}
for _, s := range sessions.Sessions {
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, s.ID[:8], desc)
}
return results
}
func describedValuesToTabs(values []string) []string {
tabbed := make([]string, 0, len(values)/2)
for i := 0; i+1 < len(values); i += 2 {
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
}
return tabbed
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
// SliverCommands returns all session control commands for the active target.
func SliverCommands(con *console.SliverClient) []*cobra.Command {
backgroundCmd := &cobra.Command{
+6 -1
View File
@@ -41,11 +41,16 @@ func SettingsAutoAdultCmd(cmd *cobra.Command, con *console.SliverClient, args []
// IsUserAnAdult - This should be called for any dangerous (OPSEC-wise) functions.
func IsUserAnAdult(con *console.SliverClient) bool {
return IsUserAnAdultWithPrompt(con, "This action is bad OPSEC, are you an adult?")
}
// IsUserAnAdultWithPrompt prompts with a custom message before continuing.
func IsUserAnAdultWithPrompt(con *console.SliverClient, prompt string) bool {
if GetAutoAdult(con) {
return true
}
confirm := false
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
_ = forms.Confirm(prompt, &confirm)
return confirm
}
+2
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command/aka"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/backdoor"
"github.com/bishopfox/sliver/client/command/cursed"
@@ -78,6 +79,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
// use.Commands,
tasks.Commands,
pivots.Commands,
aka.ImplantCommands,
)
// [ Info ]
+2 -1
View File
@@ -25,8 +25,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
flags.Bind("update", false, updateCmd, func(f *pflag.FlagSet) {
f.BoolP("prereleases", "P", false, "include pre-released (unstable) versions")
f.StringP("proxy", "p", "", "specify a proxy url (e.g. http://localhost:8080)")
f.StringP("save", "s", "", "save downloaded files to specific directory (default user home dir)")
f.StringP("save", "s", "", "save downloaded files to specific directory (default downloads dir)")
f.BoolP("insecure", "I", false, "skip tls certificate validation")
f.Bool("force", false, "skip version comparison and download the latest release")
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
+408 -36
View File
@@ -21,6 +21,7 @@ package update
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
@@ -40,8 +41,19 @@ import (
"github.com/bishopfox/sliver/client/version"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
"github.com/bishopfox/sliver/util/minisign"
"github.com/cheggaaa/pb/v3"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var (
SliverPublicKey string
)
const (
downloadAttempts = 3
downloadBackoff = 750 * time.Millisecond
)
// UpdateCmd - Check for updates.
@@ -94,7 +106,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.Printf("\nChecking for updates ... ")
prereleases, _ := cmd.Flags().GetBool("prereleases")
release, err := version.CheckForUpdates(client, prereleases)
force, _ := cmd.Flags().GetBool("force")
var release *version.Release
if force {
release, err = version.LatestRelease(client, prereleases)
} else {
release, err = version.CheckForUpdates(client, prereleases)
}
con.Printf("done!\n\n")
if err != nil {
con.PrintErrorf("Update check failed %s\n", err)
@@ -107,9 +125,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("%s\n", err)
return
}
updateAvailable(con, client, release, saveTo)
updateAvailable(con, client, release, saveTo, force)
} else {
con.PrintInfof("No new releases.\n")
if force {
con.PrintInfof("No releases found.\n")
} else {
con.PrintInfof("No new releases.\n")
}
}
now := time.Now()
lastCheck := fmt.Appendf(nil, "%d", now.Unix())
@@ -191,84 +213,434 @@ func findAssetFor(prefix string, suffixes []string, assets []version.Asset) *ver
}
func serverAssetForGOOS(assets []version.Asset) *version.Asset {
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
if runtime.GOOS == "darwin" {
suffixes = []string{"_macos.zip", "_macos"}
if runtime.GOARCH == "arm64" {
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
}
}
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
prefix := "sliver-server"
return findAssetFor(prefix, suffixes, assets)
}
func clientAssetForGOOS(assets []version.Asset) *version.Asset {
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
if runtime.GOOS == "darwin" {
suffixes = []string{"_macos.zip", "_macos"}
if runtime.GOARCH == "arm64" {
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
}
}
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
prefix := "sliver-client"
return findAssetFor(prefix, suffixes, assets)
}
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string) {
func assetSuffixes(goos, goarch string) []string {
suffixes := make([]string, 0, 8)
seen := make(map[string]struct{}, 8)
add := func(suffix string) {
if suffix == "" {
return
}
if _, ok := seen[suffix]; ok {
return
}
seen[suffix] = struct{}{}
suffixes = append(suffixes, suffix)
}
add(fmt.Sprintf("_%s-%s.zip", goos, goarch))
add(fmt.Sprintf("_%s-%s", goos, goarch))
add(fmt.Sprintf("_%s.zip", goos))
add(fmt.Sprintf("_%s", goos))
if goos == "darwin" {
add(fmt.Sprintf("_macos-%s.zip", goarch))
add(fmt.Sprintf("_macos-%s", goarch))
add("_macos.zip")
add("_macos")
}
return suffixes
}
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string, force bool) {
serverAsset := serverAssetForGOOS(release.Assets)
clientAsset := clientAssetForGOOS(release.Assets)
con.Printf("New version available %s\n", release.TagName)
msg := "New version available"
if force {
msg = "Latest release available"
}
con.Printf("%s %s\n", msg, release.TagName)
if serverAsset != nil {
con.Printf(" - Server: %s\n", util.ByteCountBinary(int64(serverAsset.Size)))
}
if clientAsset != nil {
con.Printf(" - Client: %s\n", util.ByteCountBinary(int64(clientAsset.Size)))
}
if serverAsset == nil {
con.PrintWarnf("No server asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
}
if clientAsset == nil {
con.PrintWarnf("No client asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
}
con.Println()
confirm := false
forms.Confirm("Download update?", &confirm)
if confirm {
con.Printf("Please wait ...")
err := downloadAsset(client, serverAsset, saveTo)
if serverAsset == nil && clientAsset == nil {
con.PrintWarnf("No matching assets found for %s/%s\n", runtime.GOOS, runtime.GOARCH)
return
}
publicKey, err := loadSliverPublicKey()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
err = downloadAsset(client, clientAsset, saveTo)
if err != nil {
con.PrintErrorf("%s\n", err)
return
downloads := []struct {
label string
asset *version.Asset
}{
{label: "server", asset: serverAsset},
{label: "client", asset: clientAsset},
}
for _, item := range downloads {
if item.asset == nil {
continue
}
con.Printf("Downloading %s update from %s ...\n", item.label, item.asset.BrowserDownloadURL)
if err := downloadAssetWithSignature(con, client, item.asset, release.Assets, saveTo, publicKey); err != nil {
con.PrintErrorf("Failed to download %s update: %s\n", item.label, err)
return
}
con.Println(console.Clearln)
}
con.Println(console.Clearln)
con.PrintInfof("Saved updates to: %s\n", saveTo)
}
}
func downloadAsset(client *http.Client, asset *version.Asset, saveTo string) error {
func downloadAssetWithSignature(con *console.SliverClient, client *http.Client, asset *version.Asset, assets []version.Asset, saveTo string, publicKey minisign.PublicKey) error {
if asset == nil {
return errors.New("no asset available for this platform")
}
downloadURL, err := url.Parse(asset.BrowserDownloadURL)
if err != nil {
return err
}
assetFileName := filepath.Base(downloadURL.Path)
finalPath := filepath.Join(saveTo, assetFileName)
limit := int64(asset.Size)
writer, err := os.Create(filepath.Join(saveTo, assetFileName))
sigURL := asset.BrowserDownloadURL + ".minisig"
var sigLimit int64
if sigAsset := signatureAssetFor(assetFileName, assets); sigAsset != nil && sigAsset.BrowserDownloadURL != "" {
sigURL = sigAsset.BrowserDownloadURL
sigLimit = int64(sigAsset.Size)
}
assetTempPath, err := downloadWithRetries(client, asset.BrowserDownloadURL, saveTo, int64(asset.Size), "asset")
if err != nil {
return fmt.Errorf("download asset: %w", err)
}
sigData, err := downloadBytesWithRetries(client, sigURL, sigLimit, "signature")
if err != nil {
cleanupAssetFiles(assetTempPath, finalPath)
return fmt.Errorf("download signature: %w", err)
}
if err := verifyMinisignSignature(assetTempPath, sigData, assetFileName, publicKey); err != nil {
cleanupAssetFiles(assetTempPath, finalPath)
return err
}
defer writer.Close()
con.PrintSuccessf("Signature verified for %s\n", finalPath)
resp, err := client.Get(asset.BrowserDownloadURL)
if err != nil {
if err := replaceFile(assetTempPath, finalPath); err != nil {
os.Remove(assetTempPath)
return err
}
return nil
}
func signatureAssetFor(assetFileName string, assets []version.Asset) *version.Asset {
want := assetFileName + ".minisig"
for _, asset := range assets {
if asset.Name == want {
return &asset
}
}
return nil
}
func loadSliverPublicKey() (minisign.PublicKey, error) {
if strings.TrimSpace(SliverPublicKey) == "" {
return minisign.PublicKey{}, errors.New("minisign public key not set at build time")
}
var publicKey minisign.PublicKey
if err := publicKey.UnmarshalText([]byte(SliverPublicKey)); err != nil {
return minisign.PublicKey{}, fmt.Errorf("invalid minisign public key: %w", err)
}
return publicKey, nil
}
type httpStatusError struct {
url string
status int
}
func (e httpStatusError) Error() string {
return fmt.Sprintf("unexpected HTTP status %d for %s", e.status, e.url)
}
func downloadWithRetries(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
var lastErr error
backoff := downloadBackoff
for attempt := 1; attempt <= downloadAttempts; attempt++ {
path, err := downloadOnce(client, downloadURL, saveTo, limit, label)
if err == nil {
return path, nil
}
lastErr = err
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
break
}
time.Sleep(backoff)
backoff *= 2
}
return "", lastErr
}
func downloadBytesWithRetries(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
var lastErr error
backoff := downloadBackoff
for attempt := 1; attempt <= downloadAttempts; attempt++ {
data, err := downloadBytesOnce(client, downloadURL, limit, label)
if err == nil {
return data, nil
}
lastErr = err
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
break
}
time.Sleep(backoff)
backoff *= 2
}
return nil, lastErr
}
func downloadOnce(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
parsedURL, err := url.Parse(downloadURL)
if err != nil {
return "", err
}
baseName := filepath.Base(parsedURL.Path)
tmpFile, err := os.CreateTemp(saveTo, baseName+".tmp-*")
if err != nil {
return "", err
}
success := false
defer func() {
if !success {
tmpFile.Close()
os.Remove(tmpFile.Name())
}
}()
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bar := pb.Full.Start64(limit)
barReader := bar.NewProxyReader(resp.Body)
io.Copy(writer, barReader)
bar.Finish()
if resp.StatusCode != http.StatusOK {
return "", httpStatusError{url: downloadURL, status: resp.StatusCode}
}
if limit <= 0 && resp.ContentLength > 0 {
limit = resp.ContentLength
}
var reader io.Reader = resp.Body
var bar *pb.ProgressBar
if limit > 0 {
bar = pb.Full.Start64(limit)
bar.Set("prefix", fmt.Sprintf("%s: ", label))
bar.Set(pb.Bytes, true)
bar.Set(pb.CleanOnFinish, true)
bar.Set(pb.ReturnSymbol, "\r")
if term.IsTerminal(int(os.Stdin.Fd())) {
bar.Set(pb.Terminal, true)
}
reader = bar.NewProxyReader(reader)
}
n, copyErr := io.Copy(tmpFile, reader)
if bar != nil {
bar.Finish()
}
if copyErr != nil {
return "", copyErr
}
if limit > 0 && n != limit {
return "", fmt.Errorf("downloaded %d bytes, expected %d", n, limit)
}
if err := tmpFile.Sync(); err != nil {
return "", err
}
if err := tmpFile.Close(); err != nil {
return "", err
}
success = true
return tmpFile.Name(), nil
}
func downloadBytesOnce(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, httpStatusError{url: downloadURL, status: resp.StatusCode}
}
if limit <= 0 && resp.ContentLength > 0 {
limit = resp.ContentLength
}
var reader io.Reader = resp.Body
var bar *pb.ProgressBar
if limit > 0 {
bar = pb.Full.Start64(limit)
bar.Set("prefix", fmt.Sprintf("%s: ", label))
bar.Set(pb.Bytes, true)
bar.Set(pb.CleanOnFinish, true)
bar.Set(pb.ReturnSymbol, "\r")
if term.IsTerminal(int(os.Stdin.Fd())) {
bar.Set(pb.Terminal, true)
}
reader = bar.NewProxyReader(reader)
}
data, readErr := readAllWithLimit(reader, limit)
if bar != nil {
bar.Finish()
}
if readErr != nil {
return nil, readErr
}
return data, nil
}
func isRetryableDownloadError(err error) bool {
if err == nil {
return false
}
if errors.Is(err, io.ErrUnexpectedEOF) {
return true
}
var netErr net.Error
if errors.As(err, &netErr) {
return netErr.Timeout() || netErr.Temporary()
}
var statusErr httpStatusError
if errors.As(err, &statusErr) {
switch statusErr.status {
case http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
return true
default:
return false
}
}
return false
}
func verifyMinisignSignature(artifactPath string, sigData []byte, expectedFile string, publicKey minisign.PublicKey) error {
artifact, err := os.Open(artifactPath)
if err != nil {
return fmt.Errorf("open artifact: %w", err)
}
defer artifact.Close()
reader := minisign.NewReader(artifact)
if _, err := io.Copy(io.Discard, reader); err != nil {
return fmt.Errorf("stream artifact: %w", err)
}
if !reader.Verify(publicKey, sigData) {
return errors.New("signature verification failed")
}
if err := verifyTrustedComment(sigData, expectedFile); err != nil {
return err
}
return nil
}
func readAllWithLimit(reader io.Reader, limit int64) ([]byte, error) {
if limit <= 0 {
return io.ReadAll(reader)
}
lr := &io.LimitedReader{R: reader, N: limit + 1}
data, err := io.ReadAll(lr)
if err != nil {
return nil, err
}
if int64(len(data)) > limit {
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
}
if int64(len(data)) != limit {
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
}
return data, nil
}
func verifyTrustedComment(signature []byte, expectedFile string) error {
var sig minisign.Signature
if err := sig.UnmarshalText(signature); err != nil {
return fmt.Errorf("parse signature: %w", err)
}
fileField := trustedCommentFile(sig.TrustedComment)
if fileField == "" {
return fmt.Errorf("trusted comment missing file field: %q", sig.TrustedComment)
}
if fileField != expectedFile {
return fmt.Errorf("trusted comment file mismatch: expected %q, got %q", expectedFile, fileField)
}
return nil
}
func trustedCommentFile(trustedComment string) string {
for _, field := range strings.Fields(trustedComment) {
if after, ok := strings.CutPrefix(field, "file:"); ok {
return after
}
}
return ""
}
func replaceFile(src, dst string) error {
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
return err
}
return os.Rename(src, dst)
}
func cleanupAssetFiles(tempPath, finalPath string) {
if tempPath != "" {
_ = os.Remove(tempPath)
}
if finalPath != "" {
_ = os.Remove(finalPath)
}
}
+3
View File
@@ -27,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(useCmd).PositionalCompletion(BeaconAndSessionIDCompleter(con))
registerUseIDCompletion(useCmd, con, true, true)
useSessionCmd := &cobra.Command{
Use: consts.SessionsStr,
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
},
}
carapace.Gen(useSessionCmd).PositionalCompletion(SessionIDCompleter(con))
registerUseIDCompletion(useSessionCmd, con, true, false)
useCmd.AddCommand(useSessionCmd)
useBeaconCmd := &cobra.Command{
@@ -48,6 +50,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
},
}
carapace.Gen(useBeaconCmd).PositionalCompletion(beacons.BeaconIDCompleter(con))
registerUseIDCompletion(useBeaconCmd, con, false, true)
useCmd.AddCommand(useBeaconCmd)
return []*cobra.Command{useCmd}
+71
View File
@@ -39,6 +39,77 @@ import (
var ErrNoSelection = errors.New("no selection")
func registerUseIDCompletion(cmd *cobra.Command, con *console.SliverClient, includeSessions, includeBeacons bool) {
if cmd == nil || cmd.ValidArgsFunction != nil {
return
}
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
values := useCompletionValues(con, includeSessions, includeBeacons)
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
}
}
func useCompletionValues(con *console.SliverClient, includeSessions, includeBeacons bool) []string {
results := []string{}
if con == nil || con.Rpc == nil {
return results
}
if includeSessions {
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err == nil {
for _, session := range sessions.Sessions {
link := fmt.Sprintf("[%s <- %s]", session.ActiveC2, session.RemoteAddress)
id := fmt.Sprintf("%s (%d)", session.Name, session.PID)
userHost := fmt.Sprintf("%s@%s", session.Username, session.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, fmt.Sprintf("%s\t%s", session.ID[:8], desc))
}
}
}
if includeBeacons {
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err == nil {
for _, beacon := range beacons.Beacons {
link := fmt.Sprintf("[%s <- %s]", beacon.ActiveC2, beacon.RemoteAddress)
id := fmt.Sprintf("%s (%d)", beacon.Name, beacon.PID)
userHost := fmt.Sprintf("%s@%s", beacon.Username, beacon.Hostname)
desc := strings.Join([]string{id, userHost, link}, " ")
results = append(results, fmt.Sprintf("%s\t%s", beacon.ID[:8], desc))
}
}
}
return results
}
func filterCompletionValues(values []string, prefix string) []string {
if prefix == "" {
return values
}
filtered := make([]string, 0, len(values))
for _, value := range values {
candidate := value
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
candidate = value[:tab]
}
if strings.HasPrefix(candidate, prefix) {
filtered = append(filtered, value)
}
}
return filtered
}
// UseCmd - Change the active session
func UseCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
var session *clientpb.Session
+39
View File
@@ -1,10 +1,14 @@
package websites
import (
"context"
"strings"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -26,6 +30,41 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
carapace.Gen(websitesCmd).PositionalCompletion(WebsiteNameCompleter(con))
websitesListCmd := &cobra.Command{
Use: consts.ListStr,
Short: "List configured websites",
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.ListStr}),
Run: func(cmd *cobra.Command, args []string) {
ListWebsites(cmd, con, args)
},
}
websitesCmd.AddCommand(websitesListCmd)
websitesShowCmd := &cobra.Command{
Use: "show [name]",
Short: "Show contents of a website",
Long: help.GetHelpFor([]string{consts.WebsitesStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ListWebsiteContent(args[0], con)
},
}
carapace.Gen(websitesShowCmd).PositionalCompletion(WebsiteNameCompleter(con))
websitesShowCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
out := []string{}
for _, w := range ws.Websites {
if strings.HasPrefix(w.Name, toComplete) {
out = append(out, w.Name)
}
}
return out, cobra.ShellCompDirectiveNoFileComp
}
websitesCmd.AddCommand(websitesShowCmd)
websitesRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove an entire website and all of its contents",
@@ -121,9 +121,10 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
}
web.Contents[webpath] = &clientpb.WebContent{
Path: webpath,
ContentType: contentType,
Content: data,
Path: webpath,
ContentType: contentType,
OriginalFile: filepath.Base(contentPath),
Content: data,
}
return nil
}
+10 -4
View File
@@ -21,7 +21,6 @@ package websites
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
@@ -58,11 +57,14 @@ func ListWebsites(cmd *cobra.Command, con *console.SliverClient, args []string)
con.PrintInfof("No websites\n")
return
}
con.Println("Websites")
con.Println(strings.Repeat("=", len("Websites")))
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Objects"})
for _, site := range websites.Websites {
con.Printf("%s%s%s - %d page(s)\n", console.Bold, site.Name, console.Normal, len(site.Contents))
tw.AppendRow(table.Row{site.Name, len(site.Contents)})
}
con.Println(tw.Render())
}
// ListWebsiteContent - List the static contents of a website.
@@ -91,6 +93,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
"Path",
"Content-type",
"Size",
"Original File",
"SHA256",
})
sortedContents := []*clientpb.WebContent{}
for _, content := range web.Contents {
@@ -104,6 +108,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
content.Path,
content.ContentType,
content.Size,
content.OriginalFile,
content.Sha256,
})
}
con.Println(tw.Render())
+195
View File
@@ -0,0 +1,195 @@
package console
import (
"strings"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/reeflective/console"
"github.com/spf13/cobra"
)
func (con *SliverClient) allowServerRootCommands(args []string) ([]string, error) {
con.applyCommandFallback(args)
return args, nil
}
func (con *SliverClient) prepareCompletion(line []rune, cursor int) {
if con == nil || con.App == nil {
return
}
menu := con.App.ActiveMenu()
if menu == nil {
return
}
con.setMenuCommandForCompletion(menu, line, cursor)
}
func (con *SliverClient) applyCommandFallback(args []string) {
if con == nil || con.App == nil {
return
}
con.setMenuCommand(con.App.ActiveMenu(), args)
}
func (con *SliverClient) setMenuCommand(menu *console.Menu, args []string) {
if menu == nil {
return
}
if menu.Name() != consts.ImplantMenu {
if menu.Command == nil && con.serverCmds != nil {
menu.Command = con.serverCmds()
}
return
}
if menu.Command == nil {
if con.sliverCmds == nil {
return
}
menu.Command = con.sliverCmds()
}
if con.serverCmds == nil {
return
}
name := firstNonFlagArg(args)
if name == "" {
return
}
if rootHasCommand(menu.Command, name) {
return
}
serverRoot := con.serverCmds()
if serverRoot == nil || !rootHasCommand(serverRoot, name) {
return
}
menu.Command = serverRoot
}
func (con *SliverClient) setMenuCommandForCompletion(menu *console.Menu, line []rune, cursor int) {
if menu == nil || menu.Name() != consts.ImplantMenu {
return
}
if menu.Command == nil {
if con.sliverCmds == nil {
return
}
menu.Command = con.sliverCmds()
}
if con.serverCmds == nil {
return
}
token := firstTokenFromLine(line, cursor)
if token == "" {
return
}
if rootHasCommandPrefix(menu.Command, token) {
return
}
serverRoot := con.serverCmds()
if serverRoot == nil || !rootHasCommandPrefix(serverRoot, token) {
return
}
menu.Command = serverRoot
hideFilteredCommands(menu, menu.Command)
}
func firstNonFlagArg(args []string) string {
for i := 0; i < len(args); i++ {
arg := args[i]
if arg == "--" {
if i+1 < len(args) {
return args[i+1]
}
return ""
}
if strings.HasPrefix(arg, "-") {
continue
}
return arg
}
return ""
}
func firstTokenFromLine(line []rune, cursor int) string {
if cursor > len(line) {
cursor = len(line)
}
if cursor < 0 {
cursor = 0
}
pos := 0
for pos < cursor && isSpace(line[pos]) {
pos++
}
start := pos
for pos < cursor && !isSpace(line[pos]) {
pos++
}
if start == pos {
return ""
}
return string(line[start:pos])
}
func isSpace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}
func rootHasCommand(root *cobra.Command, name string) bool {
if root == nil || name == "" {
return false
}
for _, cmd := range root.Commands() {
if cmd.Name() == name || cmd.HasAlias(name) {
return true
}
}
return false
}
func rootHasCommandPrefix(root *cobra.Command, prefix string) bool {
if root == nil || prefix == "" {
return false
}
for _, cmd := range root.Commands() {
if strings.HasPrefix(cmd.Name(), prefix) {
return true
}
for _, alias := range cmd.Aliases {
if strings.HasPrefix(alias, prefix) {
return true
}
}
}
return false
}
func hideFilteredCommands(menu *console.Menu, root *cobra.Command) {
if menu == nil || root == nil {
return
}
for _, cmd := range root.Commands() {
if cmd.Hidden {
continue
}
if filters := menu.ActiveFiltersFor(cmd); len(filters) > 0 {
cmd.Hidden = true
}
}
}
+113 -5
View File
@@ -41,6 +41,7 @@ import (
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/util"
"github.com/gofrs/uuid"
"github.com/kballard/go-shellquote"
"github.com/reeflective/console"
"github.com/reeflective/readline"
"github.com/spf13/cobra"
@@ -97,9 +98,14 @@ type SliverClient struct {
Settings *assets.ClientSettings
IsServer bool
IsCLI bool
serverCmds console.Commands
sliverCmds console.Commands
jsonHandler slog.Handler
printf func(format string, args ...any) (int, error)
jsonHandler slog.Handler
printf func(format string, args ...any) (int, error)
stdoutPipeWriter *os.File
stdoutPipeDone chan struct{}
stdoutPipeOnce sync.Once
}
// NewConsole creates the sliver client (and console), creating menus and prompts.
@@ -159,9 +165,11 @@ func NewConsole(isServer bool) *SliverClient {
// Init requires a working RPC connection to the sliver server, and 2 different sets of commands.
// If run is true, the console application is started, making this call blocking. Otherwise, commands and
// RPC connection are bound to the console (making the console ready to run), but the console does not start.
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool) error {
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
con.Rpc = rpc
con.IsCLI = !run
con.serverCmds = serverCmds
con.sliverCmds = sliverCmds
// The console application needs to query the terminal for cursor positions
// when asynchronously printing logs (that is, when no command is running).
@@ -180,6 +188,15 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
sliver := con.App.Menu(consts.ImplantMenu)
sliver.SetCommands(sliverCmds)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
baseCompleter := shell.Completer
shell.Completer = func(line []rune, cursor int) readline.Completions {
con.prepareCompletion(line, cursor)
return baseCompleter(line, cursor)
}
}
// Events
go con.startEventLoop()
go core.TunnelLoop(rpc)
@@ -206,6 +223,13 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
con.setupAsciicastRecord(asciicastLog, asciicastStream)
}
if rcScript != "" {
originalPrintf := con.printf
con.printf = fmt.Printf
con.runRCScript(serverCmds, sliverCmds, rcScript)
con.printf = originalPrintf
}
if !con.IsCLI {
return con.App.Start()
}
@@ -213,6 +237,88 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
return nil
}
func (con *SliverClient) runRCScript(serverCmds, sliverCmds console.Commands, rcScript string) {
scanner := bufio.NewScanner(strings.NewReader(rcScript))
lineNumber := 0
for scanner.Scan() {
lineNumber++
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
if err := con.runRCLine(serverCmds, sliverCmds, line); err != nil {
con.PrintErrorf("rc line %d error: %s", lineNumber, err)
}
}
if err := scanner.Err(); err != nil {
con.PrintErrorf("rc script error: %s", err)
}
}
func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line string) error {
args, err := shellquote.Split(line)
if err != nil {
return fmt.Errorf("parse error: %w", err)
}
if con.serverCmds == nil {
con.serverCmds = serverCmds
}
if con.sliverCmds == nil {
con.sliverCmds = sliverCmds
}
menu := con.App.ActiveMenu()
if menu != nil {
// Reset per line to avoid stale roots when rc scripts switch menus.
menu.Command = nil
}
for _, hook := range con.App.PreCmdRunLineHooks {
args, err = hook(args)
if err != nil {
return fmt.Errorf("pre-run line error: %w", err)
}
}
if len(args) == 0 {
return nil
}
menu = con.App.ActiveMenu()
if menu == nil {
return fmt.Errorf("no active menu")
}
if menu.Command == nil {
con.setMenuCommand(menu, args)
}
if menu.Command == nil {
return fmt.Errorf("no commands available")
}
target, _, _ := menu.Command.Find(args)
if err := menu.CheckIsAvailable(target); err != nil {
return err
}
for _, hook := range con.App.PreCmdRunHooks {
if err := hook(); err != nil {
return fmt.Errorf("pre-run error: %w", err)
}
}
menu.SetArgs(args)
menu.SetContext(context.Background())
if err := menu.Execute(); err != nil {
return err
}
return nil
}
func (con *SliverClient) startEventLoop() {
eventStream, err := con.Rpc.Events(context.Background(), &commonpb.Empty{})
if err != nil {
@@ -341,11 +447,12 @@ func (con *SliverClient) triggerReactions(event *clientpb.Event) {
con.ActiveTarget.Set(currentActiveSession, currentActiveBeacon)
}()
if event.EventType == consts.SessionOpenedEvent {
switch event.EventType {
case consts.SessionOpenedEvent:
con.ActiveTarget.Set(nil, nil)
con.ActiveTarget.Set(event.Session, nil)
} else if event.EventType == consts.BeaconRegisteredEvent {
case consts.BeaconRegisteredEvent:
con.ActiveTarget.Set(nil, nil)
beacon := &clientpb.Beacon{}
@@ -609,6 +716,7 @@ func (c *SliverClient) exitConsole(_ *console.Console) {
answer := strings.TrimSpace(text)
if (answer == "Y") || (answer == "y") {
c.FlushOutput()
os.Exit(0)
}
}
+36 -1
View File
@@ -124,7 +124,14 @@ func (con *SliverClient) setupAsciicastRecord(logFile *os.File, server io.Writer
os.Stdout = w
os.Stderr = w
go io.Copy(mw, r)
done := make(chan struct{})
con.stdoutPipeWriter = w
con.stdoutPipeDone = done
go func() {
_, _ = io.Copy(mw, r)
close(done)
}()
}
func getConsoleLogFile() *os.File {
@@ -149,6 +156,25 @@ func getConsoleAsciicastFile() *os.File {
return logFile
}
// FlushOutput drains any piped stdout before exiting.
func (con *SliverClient) FlushOutput() {
if con.stdoutPipeWriter == nil {
_ = os.Stdout.Sync()
return
}
con.stdoutPipeOnce.Do(func() {
_ = con.stdoutPipeWriter.Close()
})
if con.stdoutPipeDone != nil {
select {
case <-con.stdoutPipeDone:
case <-time.After(500 * time.Millisecond):
}
}
}
//
// -------------------------- [ Logging ] -----------------------------
//
@@ -201,6 +227,15 @@ func (con *SliverClient) PrintSuccessf(format string, args ...any) {
con.printf(Clearln+Success+format, args...)
}
// PrintSuccess prints a success message with a default or provided message.
func (con *SliverClient) PrintSuccess(args ...any) {
if len(args) == 0 {
con.PrintSuccessf("Success")
return
}
con.PrintSuccessf("%s", fmt.Sprint(args...))
}
// PrintWarnf a warning message immediately below the last line of output.
func (con *SliverClient) PrintWarnf(format string, args ...any) {
logger := slog.New(con.jsonHandler)
+1
View File
@@ -276,6 +276,7 @@ const (
WgPortFwdStr = "wg-portfwd"
MonitorStr = "monitor"
MonitorConfigStr = "config"
MCPStr = "mcp"
SSHStr = "ssh"
DLLHijackStr = "dllhijack"
InteractiveStr = "interactive"
+30 -34
View File
@@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -23,62 +24,57 @@ type GenerateFormResult struct {
}
// GenerateForm prompts for core generate flags and returns the collected values.
func GenerateForm() (*GenerateFormResult, error) {
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
result := &GenerateFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
compilerTargets := compilerTargetList(compiler)
targetBindings := &targetOptionBindings{
Common: &commonPlatformsOnly,
OS: &result.OS,
Arch: &result.Arch,
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Common Platforms Only").
Value(&commonPlatformsOnly),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
OptionsFunc(func() []huh.Option[string] {
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonOSOptions()
}
return osOptionsFromTargets(compilerTargets)
}, targetBindings).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonArchOptions(result.OS)
}
}, &result.OS).
return archOptionsFromTargets(compilerTargets, result.OS)
}, targetBindings).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonFormatOptions(result.OS)
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
}, targetBindings).
Height(3).
Value(&result.Format),
),
+30 -34
View File
@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -26,62 +27,57 @@ type GenerateBeaconFormResult struct {
}
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
result := &GenerateBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
compilerTargets := compilerTargetList(compiler)
targetBindings := &targetOptionBindings{
Common: &commonPlatformsOnly,
OS: &result.OS,
Arch: &result.Arch,
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Common Platforms Only").
Value(&commonPlatformsOnly),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
OptionsFunc(func() []huh.Option[string] {
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonOSOptions()
}
return osOptionsFromTargets(compilerTargets)
}, targetBindings).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonArchOptions(result.OS)
}
}, &result.OS).
return archOptionsFromTargets(compilerTargets, result.OS)
}, targetBindings).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonFormatOptions(result.OS)
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
}, targetBindings).
Height(3).
Value(&result.Format),
),
+220
View File
@@ -0,0 +1,220 @@
package forms
import (
"sort"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/charmbracelet/huh"
)
type formatOption struct {
format clientpb.OutputFormat
label string
value string
}
type targetOptionBindings struct {
Common *bool
OS *string
Arch *string
}
var formatOptionOrder = []formatOption{
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
}
func commonOSOptions() []huh.Option[string] {
return []huh.Option[string]{
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
}
}
func commonArchOptions(goos string) []huh.Option[string] {
switch goos {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}
func commonFormatOptions(goos string) []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if goos == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}
func firstOptionValue(options []huh.Option[string]) (string, bool) {
if len(options) == 0 {
return "", false
}
return options[0].Value, true
}
func maybeResetTargets(commonPlatformsOnly bool, lastCommonPlatformsOnly *bool, compilerTargets []*clientpb.CompilerTarget, goos, goarch, format *string) {
if lastCommonPlatformsOnly == nil || goos == nil || goarch == nil || format == nil {
return
}
if commonPlatformsOnly == *lastCommonPlatformsOnly {
return
}
*lastCommonPlatformsOnly = commonPlatformsOnly
if commonPlatformsOnly {
return
}
osOptions := commonOSOptions()
if len(compilerTargets) > 0 {
osOptions = osOptionsFromTargets(compilerTargets)
if len(osOptions) == 0 {
osOptions = commonOSOptions()
}
}
if value, ok := firstOptionValue(osOptions); ok {
*goos = value
}
archOptions := commonArchOptions(*goos)
if len(compilerTargets) > 0 {
archOptions = archOptionsFromTargets(compilerTargets, *goos)
if len(archOptions) == 0 {
archOptions = commonArchOptions(*goos)
}
}
if value, ok := firstOptionValue(archOptions); ok {
*goarch = value
}
formatOptions := commonFormatOptions(*goos)
if len(compilerTargets) > 0 {
formatOptions = formatOptionsFromTargets(compilerTargets, *goos, *goarch)
if len(formatOptions) == 0 {
formatOptions = commonFormatOptions(*goos)
}
}
if value, ok := firstOptionValue(formatOptions); ok {
*format = value
}
}
func compilerTargetList(compiler *clientpb.Compiler) []*clientpb.CompilerTarget {
if compiler == nil {
return nil
}
targets := make([]*clientpb.CompilerTarget, 0, len(compiler.Targets)+len(compiler.UnsupportedTargets))
targets = append(targets, compiler.Targets...)
targets = append(targets, compiler.UnsupportedTargets...)
return targets
}
func osOptionsFromTargets(targets []*clientpb.CompilerTarget) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
seen := map[string]struct{}{}
for _, target := range targets {
if target.GOOS == "" {
continue
}
seen[target.GOOS] = struct{}{}
}
values := make([]string, 0, len(seen))
for value := range seen {
values = append(values, value)
}
sort.Strings(values)
options := make([]huh.Option[string], 0, len(values))
for _, value := range values {
options = append(options, huh.NewOption(osLabel(value), value))
}
return options
}
func archOptionsFromTargets(targets []*clientpb.CompilerTarget, goos string) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
seen := map[string]struct{}{}
for _, target := range targets {
if goos != "" && target.GOOS != goos {
continue
}
if target.GOARCH == "" {
continue
}
seen[target.GOARCH] = struct{}{}
}
values := make([]string, 0, len(seen))
for value := range seen {
values = append(values, value)
}
sort.Strings(values)
options := make([]huh.Option[string], 0, len(values))
for _, value := range values {
options = append(options, huh.NewOption(value, value))
}
return options
}
func formatOptionsFromTargets(targets []*clientpb.CompilerTarget, goos, goarch string) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
available := map[clientpb.OutputFormat]struct{}{}
for _, target := range targets {
if goos != "" && target.GOOS != goos {
continue
}
if goarch != "" && target.GOARCH != goarch {
continue
}
available[target.Format] = struct{}{}
}
options := make([]huh.Option[string], 0, len(available))
for _, option := range formatOptionOrder {
if _, ok := available[option.format]; ok {
options = append(options, huh.NewOption(option.label, option.value))
}
}
return options
}
func osLabel(value string) string {
switch value {
case "windows":
return "Windows"
case "linux":
return "Linux"
case "darwin":
return "macOS"
default:
return value
}
}
+23 -3
View File
@@ -5,10 +5,20 @@ import (
"strings"
"github.com/charmbracelet/huh"
"golang.org/x/term"
)
const defaultSelectHeight = 10
func getTerminalWidth() int {
// Try to get actual terminal width
if width, _, err := term.GetSize(1); err == nil && width > 0 {
return width
}
// Fall back to a reasonable default
return 200
}
// Confirm prompts for a yes/no answer.
func Confirm(title string, value *bool) error {
if value == nil {
@@ -86,7 +96,7 @@ func MultiSelect(title string, options []string, value *[]string) error {
Height(listHeight(len(options))).
Value(value)
form := huh.NewForm(huh.NewGroup(field))
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
return form.Run()
}
@@ -98,6 +108,8 @@ func selectPrompt(title string, options []string, value *string, required bool)
return errors.New("select options are required")
}
// Save the original value in case the form is cancelled
originalValue := *value
ensureSelectedValue(options, value)
field := huh.NewSelect[string]().
@@ -115,8 +127,16 @@ func selectPrompt(title string, options []string, value *string, required bool)
})
}
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
err := form.Run()
// On error restore the originalValue and return err
if err != nil {
*value = originalValue
return err
}
return err
}
func ensureSelectedValue(options []string, value *string) {
+118
View File
@@ -0,0 +1,118 @@
package mcp
import (
"fmt"
"net"
"net/url"
"strings"
"github.com/bishopfox/sliver/client/version"
)
// Transport identifies the MCP server transport to expose.
type Transport string
const (
TransportHTTP Transport = "http"
TransportSSE Transport = "sse"
)
// Config controls the local MCP server settings.
type Config struct {
Transport Transport
ListenAddress string
ServerName string
ServerVersion string
}
// DefaultConfig returns the baseline MCP configuration.
func DefaultConfig() Config {
return Config{
Transport: TransportSSE,
ListenAddress: "127.0.0.1:8080",
ServerName: "Sliver MCP",
ServerVersion: version.Version,
}
}
// WithDefaults fills any empty fields with defaults.
func (c Config) WithDefaults() Config {
defaults := DefaultConfig()
if c.Transport == "" {
c.Transport = defaults.Transport
}
if c.ListenAddress == "" {
c.ListenAddress = defaults.ListenAddress
}
if c.ServerName == "" {
c.ServerName = defaults.ServerName
}
if c.ServerVersion == "" {
c.ServerVersion = defaults.ServerVersion
}
return c
}
// ParseTransport validates an input transport string.
func ParseTransport(raw string) (Transport, error) {
value := strings.ToLower(strings.TrimSpace(raw))
if value == "" {
return DefaultConfig().Transport, nil
}
switch Transport(value) {
case TransportHTTP, TransportSSE:
return Transport(value), nil
default:
return "", fmt.Errorf("unsupported transport %q", raw)
}
}
// Validate ensures the configuration can be used for a server start.
func (c Config) Validate() error {
if c.Transport != TransportHTTP && c.Transport != TransportSSE {
return fmt.Errorf("unsupported transport %q", c.Transport)
}
if c.ListenAddress == "" {
return fmt.Errorf("listen address is required")
}
if _, _, err := net.SplitHostPort(c.ListenAddress); err != nil {
return fmt.Errorf("invalid listen address %q: %w", c.ListenAddress, err)
}
if c.ServerName == "" {
return fmt.Errorf("server name is required")
}
if c.ServerVersion == "" {
return fmt.Errorf("server version is required")
}
return nil
}
// EndpointURL returns the base URL for clients to connect.
func (c Config) EndpointURL() (string, error) {
cfg := c.WithDefaults()
if err := cfg.Validate(); err != nil {
return "", err
}
host, port, err := net.SplitHostPort(cfg.ListenAddress)
if err != nil {
return "", err
}
if host == "" {
host = "127.0.0.1"
}
endpoint := url.URL{
Scheme: "http",
Host: net.JoinHostPort(host, port),
}
switch cfg.Transport {
case TransportHTTP:
endpoint.Path = "/mcp"
case TransportSSE:
endpoint.Path = "/sse"
default:
return "", fmt.Errorf("unsupported transport %q", cfg.Transport)
}
return endpoint.String(), nil
}
File diff suppressed because it is too large. Load diff
+67
View File
@@ -0,0 +1,67 @@
package mcp
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/bishopfox/sliver/client/assets"
)
const mcpLogTimeFormat = "2006-01-02_15-04-05"
func newMCPLogger() (*log.Logger, *os.File, error) {
logsDir := assets.GetMCPLogsDir()
logPath := filepath.Join(logsDir, fmt.Sprintf("%s.log", time.Now().Format(mcpLogTimeFormat)))
logFile, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
if err != nil {
return nil, nil, err
}
logger := log.New(logFile, "", log.LstdFlags|log.Lshortfile)
return logger, logFile, nil
}
func (m *mcpManager) logf(format string, args ...any) {
if m == nil {
return
}
m.mu.Lock()
logger := m.logger
m.mu.Unlock()
if logger != nil {
logger.Printf(format, args...)
}
}
func (s *SliverMCPServer) logf(format string, args ...any) {
if s == nil || s.logger == nil {
return
}
s.logger.Printf(format, args...)
}
func (s *SliverMCPServer) logToolCall(tool, sessionID, beaconID string, extras ...string) {
if s == nil || s.logger == nil {
return
}
parts := make([]string, 0, 2+len(extras))
if sessionID != "" {
parts = append(parts, fmt.Sprintf("session_id=%s", sessionID))
}
if beaconID != "" {
parts = append(parts, fmt.Sprintf("beacon_id=%s", beaconID))
}
for _, extra := range extras {
if extra != "" {
parts = append(parts, extra)
}
}
if len(parts) == 0 {
s.logger.Printf("mcp tool=%s", tool)
return
}
s.logger.Printf("mcp tool=%s %s", tool, strings.Join(parts, " "))
}
+200
View File
@@ -0,0 +1,200 @@
package mcp
import (
"context"
"errors"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/bishopfox/sliver/protobuf/rpcpb"
mcpserver "github.com/mark3labs/mcp-go/server"
)
var (
ErrAlreadyRunning = errors.New("mcp server already running")
ErrNotRunning = errors.New("mcp server not running")
)
// Status describes the running state of the MCP server.
type Status struct {
Running bool
StartedAt time.Time
Config Config
LastError string
}
type serverTransport interface {
Start(addr string) error
Shutdown(ctx context.Context) error
}
type mcpManager struct {
mu sync.Mutex
cfg Config
running bool
startedAt time.Time
lastErr string
server *SliverMCPServer
transport serverTransport
done chan struct{}
logger *log.Logger
logFile *os.File
}
func newManager() *mcpManager {
return &mcpManager{
cfg: DefaultConfig(),
}
}
var defaultManager = newManager()
// GetStatus returns the current MCP server state.
func GetStatus() Status {
return defaultManager.status()
}
// Start launches the MCP server using the provided configuration.
func Start(cfg Config, rpc rpcpb.SliverRPCClient) error {
return defaultManager.start(cfg, rpc)
}
// ServeStdio runs the MCP server over stdio using the provided configuration.
func ServeStdio(cfg Config, rpc rpcpb.SliverRPCClient) error {
cfg = cfg.WithDefaults()
if err := cfg.Validate(); err != nil {
return err
}
logger, logFile, err := newMCPLogger()
if err != nil {
return err
}
defer logFile.Close()
logger.Printf("starting mcp stdio server")
srv := newServer(cfg, rpc, logger)
err = mcpserver.ServeStdio(srv.server)
if err != nil {
logger.Printf("mcp stdio server error: %v", err)
} else {
logger.Printf("mcp stdio server stopped")
}
return err
}
// Stop shuts down the MCP server.
func Stop(ctx context.Context) error {
return defaultManager.stop(ctx)
}
func (m *mcpManager) status() Status {
m.mu.Lock()
defer m.mu.Unlock()
return Status{
Running: m.running,
StartedAt: m.startedAt,
Config: m.cfg,
LastError: m.lastErr,
}
}
func (m *mcpManager) start(cfg Config, rpc rpcpb.SliverRPCClient) error {
cfg = cfg.WithDefaults()
if err := cfg.Validate(); err != nil {
return err
}
m.mu.Lock()
if m.running {
m.mu.Unlock()
return ErrAlreadyRunning
}
logger, logFile, err := newMCPLogger()
if err != nil {
m.mu.Unlock()
return err
}
mcpServer := newServer(cfg, rpc, logger)
var transport serverTransport
switch cfg.Transport {
case TransportHTTP:
transport = mcpserver.NewStreamableHTTPServer(mcpServer.server)
case TransportSSE:
transport = mcpserver.NewSSEServer(mcpServer.server)
default:
m.mu.Unlock()
logFile.Close()
return errors.New("unsupported transport")
}
m.cfg = cfg
m.running = true
m.startedAt = time.Now()
m.lastErr = ""
m.server = mcpServer
m.transport = transport
m.done = make(chan struct{})
m.logger = logger
m.logFile = logFile
addr := cfg.ListenAddress
done := m.done
m.mu.Unlock()
logger.Printf("starting mcp server transport=%s listen=%s", cfg.Transport, cfg.ListenAddress)
go m.run(addr, transport, done)
return nil
}
func (m *mcpManager) run(addr string, transport serverTransport, done chan struct{}) {
err := transport.Start(addr)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
m.logf("mcp server error: %v", err)
m.mu.Lock()
m.lastErr = err.Error()
m.mu.Unlock()
}
m.logf("mcp server stopped")
m.mu.Lock()
m.running = false
m.transport = nil
m.server = nil
logFile := m.logFile
m.logFile = nil
m.logger = nil
m.mu.Unlock()
if logFile != nil {
logFile.Close()
}
close(done)
}
func (m *mcpManager) stop(ctx context.Context) error {
m.mu.Lock()
if !m.running {
m.mu.Unlock()
return ErrNotRunning
}
transport := m.transport
done := m.done
m.mu.Unlock()
if transport == nil {
return ErrNotRunning
}
m.logf("mcp server shutdown requested")
if err := transport.Shutdown(ctx); err != nil {
return err
}
if done == nil {
return nil
}
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
+247
View File
@@ -0,0 +1,247 @@
package mcp
import (
"context"
"log"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
mcpapi "github.com/mark3labs/mcp-go/mcp"
mcpserver "github.com/mark3labs/mcp-go/server"
)
const (
listSessionsAndBeaconsToolName = "list_sessions_and_beacons"
)
type sessionSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Transport string `json:"transport"`
RemoteAddress string `json:"remote_address"`
Hostname string `json:"hostname"`
Username string `json:"username"`
PID int32 `json:"pid"`
OS string `json:"os"`
Arch string `json:"arch"`
Locale string `json:"locale"`
LastCheckin int64 `json:"last_checkin"`
IsDead bool `json:"is_dead"`
Integrity string `json:"integrity"`
}
type beaconSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Transport string `json:"transport"`
RemoteAddress string `json:"remote_address"`
Hostname string `json:"hostname"`
Username string `json:"username"`
PID int32 `json:"pid"`
OS string `json:"os"`
Arch string `json:"arch"`
Locale string `json:"locale"`
LastCheckin int64 `json:"last_checkin"`
NextCheckin int64 `json:"next_checkin"`
Interval int64 `json:"interval"`
Jitter int64 `json:"jitter"`
TasksCount int64 `json:"tasks_count"`
TasksCountCompleted int64 `json:"tasks_count_completed"`
IsDead bool `json:"is_dead"`
Integrity string `json:"integrity"`
}
type listSessionsAndBeaconsResult struct {
Sessions []sessionSummary `json:"sessions"`
SessionsCount int `json:"sessions_count"`
Beacons []beaconSummary `json:"beacons"`
BeaconsCount int `json:"beacons_count"`
}
// SliverMCPServer wraps the MCP server with Sliver RPC access for handlers.
type SliverMCPServer struct {
Rpc rpcpb.SliverRPCClient
server *mcpserver.MCPServer
logger *log.Logger
}
func newServer(cfg Config, rpc rpcpb.SliverRPCClient, logger *log.Logger) *SliverMCPServer {
base := mcpserver.NewMCPServer(
cfg.ServerName,
cfg.ServerVersion,
mcpserver.WithToolCapabilities(false),
)
listSessionsAndBeaconsTool := mcpapi.NewTool(
listSessionsAndBeaconsToolName,
mcpapi.WithDescription("List active sessions and beacons."),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
lsTool := mcpapi.NewTool(
lsToolName,
mcpapi.WithDescription("List the contents of a remote directory."),
mcpapi.WithInputSchema[lsArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
cdTool := mcpapi.NewTool(
cdToolName,
mcpapi.WithDescription("Change the current directory on a remote session or beacon."),
mcpapi.WithInputSchema[cdArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(false),
)
catTool := mcpapi.NewTool(
catToolName,
mcpapi.WithDescription("Download and return the contents of a remote file."),
mcpapi.WithInputSchema[catArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
pwdTool := mcpapi.NewTool(
pwdToolName,
mcpapi.WithDescription("Return the current working directory of a remote session or beacon."),
mcpapi.WithInputSchema[pwdArgs](),
mcpapi.WithReadOnlyHintAnnotation(true),
mcpapi.WithDestructiveHintAnnotation(false),
mcpapi.WithIdempotentHintAnnotation(true),
)
rmTool := mcpapi.NewTool(
rmToolName,
mcpapi.WithDescription("Remove a file or directory on the remote target."),
mcpapi.WithInputSchema[rmArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
mvTool := mcpapi.NewTool(
mvToolName,
mcpapi.WithDescription("Move or rename a file or directory on the remote target."),
mcpapi.WithInputSchema[mvArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
cpTool := mcpapi.NewTool(
cpToolName,
mcpapi.WithDescription("Copy a file on the remote target."),
mcpapi.WithInputSchema[cpArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
mkdirTool := mcpapi.NewTool(
mkdirToolName,
mcpapi.WithDescription("Create a directory on the remote target."),
mcpapi.WithInputSchema[mkdirArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
chmodTool := mcpapi.NewTool(
chmodToolName,
mcpapi.WithDescription("Change file or directory permissions on the remote target."),
mcpapi.WithInputSchema[chmodArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
chownTool := mcpapi.NewTool(
chownToolName,
mcpapi.WithDescription("Change file or directory ownership on the remote target."),
mcpapi.WithInputSchema[chownArgs](),
mcpapi.WithReadOnlyHintAnnotation(false),
mcpapi.WithDestructiveHintAnnotation(true),
)
srv := &SliverMCPServer{
Rpc: rpc,
server: base,
logger: logger,
}
srv.server.AddTool(listSessionsAndBeaconsTool, srv.listSessionsAndBeaconsHandler)
srv.server.AddTool(lsTool, srv.lsHandler)
srv.server.AddTool(cdTool, srv.cdHandler)
srv.server.AddTool(catTool, srv.catHandler)
srv.server.AddTool(pwdTool, srv.pwdHandler)
srv.server.AddTool(rmTool, srv.rmHandler)
srv.server.AddTool(mvTool, srv.mvHandler)
srv.server.AddTool(cpTool, srv.cpHandler)
srv.server.AddTool(mkdirTool, srv.mkdirHandler)
srv.server.AddTool(chmodTool, srv.chmodHandler)
srv.server.AddTool(chownTool, srv.chownHandler)
return srv
}
func (s *SliverMCPServer) listSessionsAndBeaconsHandler(ctx context.Context, _ mcpapi.CallToolRequest) (*mcpapi.CallToolResult, error) {
if s.Rpc == nil {
return mcpapi.NewToolResultError("rpc client not configured"), nil
}
s.logToolCall(listSessionsAndBeaconsToolName, "", "")
sessionsResp, err := s.Rpc.GetSessions(ctx, &commonpb.Empty{})
if err != nil {
return mcpapi.NewToolResultErrorFromErr("failed to list sessions", err), nil
}
beaconsResp, err := s.Rpc.GetBeacons(ctx, &commonpb.Empty{})
if err != nil {
return mcpapi.NewToolResultErrorFromErr("failed to list beacons", err), nil
}
result := listSessionsAndBeaconsResult{
Sessions: make([]sessionSummary, 0, len(sessionsResp.GetSessions())),
Beacons: make([]beaconSummary, 0, len(beaconsResp.GetBeacons())),
}
for _, session := range sessionsResp.GetSessions() {
if session == nil {
continue
}
result.Sessions = append(result.Sessions, sessionSummary{
ID: session.ID,
Name: session.Name,
Transport: session.Transport,
RemoteAddress: session.RemoteAddress,
Hostname: session.Hostname,
Username: session.Username,
PID: session.PID,
OS: session.OS,
Arch: session.Arch,
Locale: session.Locale,
LastCheckin: session.LastCheckin,
IsDead: session.IsDead,
Integrity: session.Integrity,
})
}
for _, beacon := range beaconsResp.GetBeacons() {
if beacon == nil {
continue
}
result.Beacons = append(result.Beacons, beaconSummary{
ID: beacon.ID,
Name: beacon.Name,
Transport: beacon.Transport,
RemoteAddress: beacon.RemoteAddress,
Hostname: beacon.Hostname,
Username: beacon.Username,
PID: beacon.PID,
OS: beacon.OS,
Arch: beacon.Arch,
Locale: beacon.Locale,
LastCheckin: beacon.LastCheckin,
NextCheckin: beacon.NextCheckin,
Interval: beacon.Interval,
Jitter: beacon.Jitter,
TasksCount: beacon.TasksCount,
TasksCountCompleted: beacon.TasksCountCompleted,
IsDead: beacon.IsDead,
Integrity: beacon.Integrity,
})
}
result.SessionsCount = len(result.Sessions)
result.BeaconsCount = len(result.Beacons)
return mcpapi.NewToolResultStructuredOnly(result), nil
}
+65 -20
View File
@@ -63,6 +63,68 @@ func (r *Release) Published() (time.Time, error) {
// occurs we don't really try to recover. If client is nil we just use the Go
// default client with default settings.
func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
releases, err := fetchReleases(client)
if err != nil {
return nil, err
}
if len(releases) == 0 {
return nil, nil
}
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range releases {
if release == nil {
continue
}
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
}
// LatestRelease - Returns the latest release without comparing to the current version.
func LatestRelease(client *http.Client, prereleases bool) (*Release, error) {
releases, err := fetchReleases(client)
if err != nil {
return nil, err
}
if len(releases) == 0 {
return nil, nil
}
var latest *Release
var latestVersion string
for _, release := range releases {
if release == nil {
continue
}
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if latest == nil || semver.Compare(releaseVersion, latestVersion) > 0 {
latest = release
latestVersion = releaseVersion
}
}
return latest, nil
}
func fetchReleases(client *http.Client) ([]*Release, error) {
skip := os.Getenv(skipCheckEnv)
if skip != "" || GithubReleasesURL == "" {
return nil, nil
@@ -84,27 +146,10 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
if resp.StatusCode != 200 {
return nil, errors.New("API returned non-200 status code")
}
releases := &[]*Release{}
err = json.Unmarshal(body, releases)
if err != nil {
releases := []*Release{}
if err := json.Unmarshal(body, &releases); err != nil {
return nil, err
}
current := canonicalSemver(Version)
if current == "" {
current = "v0.0.0"
}
for _, release := range *releases {
if release.Prerelease && !prereleases {
continue
}
releaseVersion := canonicalSemver(release.TagName)
if releaseVersion == "" {
continue
}
if semver.Compare(releaseVersion, current) > 0 {
return release, nil
}
}
return nil, nil
return releases, nil
}
@@ -22,7 +22,7 @@ export type TutorialCardCardProps = {
export default function TutorialCard(props: TutorialCardCardProps) {
return (
<Card isFooterBlurred>
<Card isFooterBlurred className="relative z-0">
<CardHeader className="absolute z-10 top-1 flex-col items-end">
<p className="text-md text-white/70 uppercase font-bold">
{props.name}
@@ -41,7 +41,7 @@ export default function TutorialCard(props: TutorialCardCardProps) {
/>
</div>
<CardFooter className="absolute bg-black/40 bottom-0 z-100 border-t-1 border-default-600 dark:border-default-100">
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex flex-row w-full items-center">
<p
className={
+52
View File
@@ -0,0 +1,52 @@
__IMPORTANT:__ MCP (Model Context Protocol) support is _experimental_ and NOT ALL FUNCTIONALITY IS SUPPORTED YET. Pull requests and testing feedback are welcome!
There are two main ways to connect an AI model to sliver using MCP: locally over stdio, or over a network using HTTP/SSE transports. The `sliver-client` supports both stdio and http/sse, but the `sliver-server` binary only supports HTTP/SSE. We recommend using the `sliver-client` and [Multiplayer Mode](/docs?name=Multi-player+Mode) to connect to a remote server with the stdio transport.
## MCP Over STDIO
Example configurations for various AIs, you'll need to configure [Multiplayer Mode](/docs?name=Multi-player+Mode) first:
### OpenAI Codex
```
[mcp_servers.sliver]
args = ["mcp", "--config", "/path/to/multiplayer.cfg"]
command = "/path/to/sliver-client"
```
### Anthropic Claude Code
```
claude mcp add sliver -- /path/to/sliver-client mcp --config /path/to/multiplayer.cfg
```
## MCP Over HTTP/SSE
The MCP HTTP/SSE server can be started from either the `sliver-client` or `sliver-server`, however _where you run the command is where the server will start_ i.e., starting the HTTP/SSE MCP using `sliver-client` will turn that `sliver-client` binary into the MCP server (connected to whatever Sliver server you're currently connected to). Running the command from the `sliver-server` console will start a HTTP/SSE MCP server locally bound on the server.
```
.------..------..------..------..------..------.
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
`------'`------'`------'`------'`------'`------'
All hackers gain skulk
[*] Server v1.6.4 - 4d0d46afd6e1cbcaafde92687144cb28694a68ce
[*] Welcome to the sliver shell, please type 'help' for options
sliver > mcp
Status: stopped
Transport: sse
Listen: 127.0.0.1:8080
Endpoint: http://127.0.0.1:8080/sse
sliver > mcp start --transport http
[*] Starting MCP server (http) on 127.0.0.1:8080
[*] Endpoint: http://127.0.0.1:8080/mcp
```
You can use `mcp stop` to stop the server, see `mcp start --help` for additional options when starting the MCP server. Authentication is not yet supported for MCP over HTTP/SSE.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
Sliver implants support two types of connections, sessions and beacons.
Sessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
For example if configured to use `/admin` and `/demo` as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
When using Sliver during a live engagement, you’re going to need to use custom stagers, which are essentially a first binary or commandline that will retrieve and/or load Sliver into memory on your target system. Sliver can generate shellcode for your stager to execute by using the `profiles` command.
For this exercise, we will create a new beacon profile and prepare to stage it.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
Pivots allow routing implant traffic through other implants. This can be useful in environments that don’t have any outbound access, but are reachable from other parts of the network that you have access to.
Sliver supports two types of pivots: TCP, which can be used on all operating systems, and named pipes, which are Windows-only.
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
Reactions are a basic way to automate tasks in the sliver console, they allow you to specify sliver commands to run on a list of events.
```bash
@@ -1,5 +1,3 @@
# This course is intended for the 1.6 version of Sliver, which is not yet published
The Sliver armory is used to install and maintain third party extensions and aliases within sliver. The full list of available extensions can be found at https://github.com/sliverarmory/armory, keep in mind this is community maintained so not all modules are necessarily up to date.
You can download all configured extensions/aliases using the armory command.
+1 -1
View File
@@ -1 +1 @@
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\nSliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n Line truncated
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-Line truncated
+8
View File
@@ -154,6 +154,14 @@ else
exit 1
fi
# server / gogo / goname
if go test -tags=server,$TAGS ./server/gogo/goname ; then
:
else
cat ~/.sliver/logs/sliver.log
exit 1
fi
# server / gogo
if go test -tags=server,$TAGS ./server/gogo ; then
:
+38 -20
View File
@@ -1,14 +1,18 @@
module github.com/bishopfox/sliver
go 1.25.3
go 1.25.5
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260128035037-680800942770
require (
filippo.io/age v1.2.1
filippo.io/age v1.3.1
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/alecthomas/chroma v0.10.0
@@ -25,47 +29,60 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
github.com/klauspost/compress v1.18.0
github.com/klauspost/compress v1.18.1
github.com/lesnuages/go-winio v0.4.19
github.com/lesnuages/snitch v0.6.0
github.com/mattn/go-sqlite3 v1.14.32
github.com/miekg/dns v1.1.68
github.com/miekg/dns v1.1.70
github.com/moloch--/asciicast v0.1.1
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/moloch--/sgn v0.0.4
github.com/ncruces/go-sqlite3 v0.29.1
github.com/moloch--/sgn v0.0.5
github.com/ncruces/go-sqlite3 v0.30.4
github.com/reeflective/console v0.1.25
github.com/reeflective/readline v1.1.3
github.com/reeflective/readline v1.1.4
github.com/rsteube/carapace v0.50.2
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.1
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.9.0
github.com/tetratelabs/wazero v1.11.0
github.com/things-go/go-socks5 v0.1.0
github.com/ulikunitz/xz v0.5.15
github.com/xlab/treeprint v1.2.0
github.com/yiya1989/sshkrb5 v0.0.1
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
golang.org/x/mod v0.30.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.39.0
golang.org/x/mod v0.31.0
golang.org/x/net v0.48.0
golang.org/x/sys v0.40.0
golang.org/x/term v0.38.0
golang.org/x/text v0.32.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
google.golang.org/protobuf v1.36.11
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
modernc.org/sqlite v1.39.0
tailscale.com v1.90.6
tailscale.com v1.92.5
)
require (
filippo.io/hpke v0.4.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/creachadair/msync v0.7.1 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
)
require (
@@ -84,8 +101,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/carapace-sh/carapace v1.9.0 // indirect
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
github.com/carapace-sh/carapace v1.11.0
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
@@ -130,9 +147,10 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jsimonetti/rtnetlink v1.4.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mark3labs/mcp-go v0.43.2
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
@@ -169,7 +187,7 @@ require (
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
+70 -48
View File
@@ -1,14 +1,16 @@
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
filippo.io/age v1.3.1 h1:hbzdQOJkuaMEpRCLSN1/C5DX74RPcNCk6oqhKMXmZi0=
filippo.io/age v1.3.1/go.mod h1:EZorDTYUxt836i3zdori5IJX/v2Lj6kWFU0cfh6C0D4=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb h1:c1Bb1n1wr2xgTHkNVxkUHBshff33m+IXgY6lE6oYttg=
@@ -17,8 +19,8 @@ github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qd
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c h1:cFMQKxryHZ3Sddca8nqJ1RDjaUTW/+79lRKs+7508nk=
github.com/Binject/go-donut v0.0.0-20220908180326-fcdcc35d591c/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e h1:ytVmxGQuS7ELO/WpvH6iuY1hVcJ6iOTw3VLOOIFlo8o=
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
@@ -84,11 +86,15 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -142,6 +148,8 @@ github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNs
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
github.com/creachadair/msync v0.7.1/go.mod h1:8CcFlLsSujfHE5wWm19uUBLHIPDAUr6LXDwneVMO008=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@@ -253,6 +261,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -264,8 +274,8 @@ github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFr
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
@@ -274,6 +284,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@@ -282,8 +293,8 @@ github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWr
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
@@ -306,6 +317,10 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -327,20 +342,24 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
github.com/moloch--/console v0.0.0-20260128035037-680800942770 h1:JtfE7Od0O5ISD4b9r9AX7FTlAf8sT6O4qr5HV6Ey5U0=
github.com/moloch--/console v0.0.0-20260128035037-680800942770/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989 h1:g0EfxAWzDGYarN0RAPibsjz2M/f0pAqYNiEpCPWRDok=
github.com/moloch--/readline v0.0.0-20260128035102-9f373ad18989/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/moloch--/sgn v0.0.5 h1:92GFaRpe+353uhqzFiNKXKgmTiD4dBxnvb8gd4MaV/g=
github.com/moloch--/sgn v0.0.5/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -349,8 +368,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-sqlite3 v0.29.1 h1:NIi8AISWBToRHyoz01FXiTNvU147Tqdibgj2tFzJCqM=
github.com/ncruces/go-sqlite3 v0.29.1/go.mod h1:PpccBNNhvjwUOwDQEn2gXQPFPTWdlromj0+fSkd5KSg=
github.com/ncruces/go-sqlite3 v0.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA=
github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@@ -364,6 +383,8 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
@@ -374,14 +395,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
github.com/reeflective/readline v1.1.3 h1:meGkuEmujZHmalJ9eT3pYkwtkufH5EwYFPTnaph0T0s=
github.com/reeflective/readline v1.1.3/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -399,12 +416,12 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -441,8 +458,8 @@ github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
@@ -455,6 +472,8 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
@@ -463,6 +482,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yiya1989/sshkrb5 v0.0.1 h1:oAKpXLBeZExjYDV9eTL2KypmXU+5oPrJtqDuR5JbWic=
github.com/yiya1989/sshkrb5 v0.0.1/go.mod h1:r1bHcNa22x8rrg/Kj/cDF5bX6UtZrHiQgRFvj3Mnpl8=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@@ -481,6 +502,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
@@ -504,8 +526,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -514,8 +536,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
@@ -544,8 +566,8 @@ golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -564,8 +586,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -594,8 +616,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -628,8 +650,8 @@ gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgI
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
@@ -662,5 +684,5 @@ mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.90.6 h1:EhYPiZP/xcLeinikaLA0kn4CQT3+z9SZ13IB/kzWhd4=
tailscale.com v1.90.6/go.mod h1:+9EX6pOGCNa6pxCVRhhlJLy/qnkDzOplFYpeZyYlCT0=
tailscale.com v1.92.5 h1:h88HqtGk8jd9umF7l6m/8+QK69dY9IVMIlkNK9XYpvQ=
tailscale.com v1.92.5/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0=
+1 -1
View File
@@ -6,7 +6,7 @@ Core Go implementation of the Sliver implant runtime and supporting subsystems.
## Go Files
- `sliver.go` – Implant entry point that wires transports, handlers, and the task runner together at startup.
- `main.go` – Implant entry point that wires transports, handlers, and the task runner together at startup.
## Sub-packages
+51 -17
View File
@@ -27,10 +27,12 @@ import (
"fmt"
"io"
"io/fs"
"maps"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
@@ -164,10 +166,14 @@ func dirListHandler(data []byte, resp RPCResponse) {
dirList.Exists = true
} else {
dirList.Exists = false
dirList.Response = &commonpb.Response{Err: err.Error()}
}
dirList.Files = []*sliverpb.FileInfo{}
rootDirInfo, err := rootDirEntry.Info()
if err == nil && filter == "" {
var rootDirInfo fs.FileInfo
if rootDirEntry != nil {
rootDirInfo, err = rootDirEntry.Info()
}
if err == nil && rootDirInfo != nil && filter == "" {
// We should not get an error because we created the DirEntry object from the FileInfo object
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
@@ -191,6 +197,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
if err != nil {
// Then this is a bad filter, and it will be a bad filter
// on every iteration of the loop, so we might as well break now
dirList.Response = &commonpb.Response{Err: err.Error()}
break
}
}
@@ -247,22 +254,26 @@ func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
// {{end}}
return "", nil, nil, err
}
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
/*
We could place the entry for the directory itself
at the beginning of the returned slice of DirEntry
objects, but then it is not clear if that is the
root directory or a directory / file in the root
directory with the same name as the root directory.
Using WalkDir is not great here because you cannot
tell it to not be recursive, so we will be wasting
cycles telling it to skip directories and files
*/
files, err := os.ReadDir(dir)
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
rootInfo, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
}
return dir, nil, []fs.DirEntry{}, err
}
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
/*
We could place the entry for the directory itself
at the beginning of the returned slice of DirEntry
objects, but then it is not clear if that is the
root directory or a directory / file in the root
directory with the same name as the root directory.
Using WalkDir is not great here because you cannot
tell it to not be recursive, so we will be wasting
cycles telling it to skip directories and files
*/
files, err := os.ReadDir(dir)
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
}
func rmHandler(data []byte, resp RPCResponse) {
@@ -1146,6 +1157,29 @@ func executeHandler(data []byte, resp RPCResponse) {
return
}
cmd := exec.Command(exePath, execReq.Args...)
if execReq.EnvInheritance || len(execReq.Env) > 0 {
envVars := make(map[string]string)
if execReq.EnvInheritance {
for _, entry := range os.Environ() {
parts := strings.SplitN(entry, "=", 2)
if len(parts) == 2 {
envVars[parts[0]] = parts[1]
} else if len(parts) == 1 {
envVars[parts[0]] = ""
}
}
}
maps.Copy(envVars, execReq.Env)
keys := make([]string, 0, len(envVars))
for key := range envVars {
keys = append(keys, key)
}
sort.Strings(keys)
cmd.Env = make([]string, 0, len(envVars))
for _, key := range keys {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, envVars[key]))
}
}
if execReq.Output {
stdOutBuff := new(bytes.Buffer)
@@ -1,4 +1,4 @@
#include "sliver.h"
#include "main.h"
#ifdef __WIN32
#include <windows.h>
+47
View File
@@ -0,0 +1,47 @@
package main
/*
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/>.
*/
// {{if or .Config.IsSharedLib }}
//#include "main.h"
import "C"
// {{end}}
import (
"github.com/bishopfox/sliver/implant/sliver/runner"
)
// {{if or .Config.IsSharedLib .Config.IsShellcode}}
var isRunning bool = false
// {{range .Config.Exports}}
//export {{.}}
func {{.}} () {
if !isRunning {
isRunning = true
main()
}
}
// {{end}}
// {{end}}
func main() {
runner.Main()
}
File renamed without changes.
@@ -1,4 +1,4 @@
package main
package runner
/*
Sliver Implant Framework
@@ -18,12 +18,6 @@ package main
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// {{if or .Config.IsSharedLib }}
//#include "sliver.h"
import "C"
// {{end}}
import (
"errors"
@@ -105,26 +99,11 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
}
}
}
return
}
// {{end}}
// {{if or .Config.IsSharedLib .Config.IsShellcode}}
var isRunning bool = false
// {{range .Config.Exports}}
//export {{.}}
func {{.}} () {
if !isRunning {
isRunning = true
main()
}
}
// {{end}}
// {{end}}
func main() {
func Main() {
// {{if .Config.Debug}}
log.SetFlags(log.LstdFlags | log.Lshortfile)
@@ -272,17 +251,19 @@ func beaconMainLoop(beacon *transports.Beacon) error {
time.Sleep(time.Second)
beacon.Close()
// BeaconMain - Is executed in it's own goroutine as the function will block
// until all tasks complete (in success or failure), if a task handler blocks
// forever it will simply block this set of tasks instead of the entire beacon
// BeaconMain - Tasks run in background goroutines, check-ins happen on schedule
// Results are collected and sent on subsequent check-ins
pendingResults := make(chan *sliverpb.Envelope, 100)
errors := make(chan error)
shortCircuit := make(chan struct{})
for {
duration := beacon.Duration()
nextCheckin = time.Now().Add(duration)
go func() {
oldInterval := beacon.Interval()
err := beaconMain(beacon, nextCheckin)
err := beaconMain(beacon, nextCheckin, pendingResults)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[beacon] main error: %v", nextCheckin)
@@ -310,7 +291,7 @@ func beaconMainLoop(beacon *transports.Beacon) error {
return nil
}
func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
func beaconMain(beacon *transports.Beacon, nextCheckin time.Time, pendingResults chan *sliverpb.Envelope) error {
err := beacon.Start()
if err != nil {
// {{if .Config.Debug}}
@@ -325,12 +306,26 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
time.Sleep(time.Second)
beacon.Close()
}()
// Collect any pending results from background tasks
var completedResults []*sliverpb.Envelope
collecting:
for {
select {
case result := <-pendingResults:
completedResults = append(completedResults, result)
default:
break collecting
}
}
// {{if .Config.Debug}}
log.Printf("[beacon] sending check in ...")
log.Printf("[beacon] sending check in with %d pending results...", len(completedResults))
// {{end}}
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
ID: InstanceID,
NextCheckin: int64(beacon.Duration().Seconds()),
Tasks: completedResults,
}))
if err != nil {
// {{if .Config.Debug}}
@@ -382,30 +377,81 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
}
}
// ensure extensions are registered before they are called
var results []*sliverpb.Envelope
// Dispatch tasks to background - results sent to pendingResults as each completes
// Extensions must be registered synchronously before other tasks can use them
for _, r := range beaconHandleTasklist(tasksExtensionRegister) {
results = append(results, r)
}
for _, r := range beaconHandleTasklist(tasksOther) {
results = append(results, r)
pendingResults <- r
}
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
ID: InstanceID,
Tasks: results,
}))
if err != nil {
// {{if .Config.Debug}}
log.Printf("[beacon] error sending results %s", err)
// {{end}}
}
// Dispatch other tasks individually - each sends its result when done
beaconDispatchTasks(tasksOther, pendingResults)
// {{if .Config.Debug}}
log.Printf("[beacon] all results sent to server, cleanup ...")
log.Printf("[beacon] tasks dispatched to background, check-in complete")
// {{end}}
return nil
}
// beaconDispatchTasks dispatches tasks to run in background, sending results as each completes
func beaconDispatchTasks(tasks []*sliverpb.Envelope, pendingResults chan *sliverpb.Envelope) {
sysHandlers := handlers.GetSystemHandlers()
specHandlers := handlers.GetKillHandlers()
for _, task := range tasks {
// {{if .Config.Debug}}
log.Printf("[beacon] dispatching task %d", task.Type)
// {{end}}
if handler, ok := sysHandlers[task.Type]; ok {
data := task.Data
taskID := task.ID
// {{if eq .Config.GOOS "windows" }}
go func() {
handlers.WrapperHandler(handler, data, func(data []byte, err error) {
// {{if .Config.Debug}}
if err != nil {
log.Printf("[beacon] handler function returned an error: %s", err)
}
log.Printf("[beacon] task completed (id: %d)", taskID)
// {{end}}
pendingResults <- &sliverpb.Envelope{
ID: taskID,
Data: data,
}
})
}()
// {{else}}
go func() {
handler(data, func(data []byte, err error) {
// {{if .Config.Debug}}
if err != nil {
log.Printf("[beacon] handler function returned an error: %s", err)
}
log.Printf("[beacon] task completed (id: %d)", taskID)
// {{end}}
pendingResults <- &sliverpb.Envelope{
ID: taskID,
Data: data,
}
})
}()
// {{end}}
} else if task.Type == sliverpb.MsgOpenSession {
go openSessionHandler(task.Data)
pendingResults <- &sliverpb.Envelope{
ID: task.ID,
Data: []byte{},
}
} else if handler, ok := specHandlers[task.Type]; ok {
go handler(task.Data, nil)
} else {
pendingResults <- &sliverpb.Envelope{
ID: task.ID,
UnknownMessageType: true,
}
}
}
}
func beaconHandleTasklist(tasks []*sliverpb.Envelope) []*sliverpb.Envelope {
results := []*sliverpb.Envelope{}
resultsMutex := &sync.Mutex{}
-3
View File
@@ -362,9 +362,6 @@ func dnsBeacon(uri *url.URL) *Beacon {
return client.WriteEnvelope(envelope)
},
Close: func() error {
if client != nil {
return client.CloseSession()
}
return nil
},
Cleanup: func() error {
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -148,6 +148,7 @@ message ImplantConfig {
bool ObfuscateSymbols = 12;
string TemplateName = 13;
bool SGNEnabled = 14;
string GoPackage = 15;
bool IncludeMTLS = 53;
bool IncludeHTTP = 16;
@@ -585,6 +586,9 @@ message WebContent {
string ContentType = 4;
uint64 Size = 5 [ jstype = JS_STRING ];
string OriginalFile = 6;
string Sha256 = 7;
bytes Content = 9;
}
@@ -1315,4 +1319,4 @@ message CertificateData {
message CertificateInfo {
repeated CertificateData info = 1;
}
}
+151 -61
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.3
// protoc v5.29.3
// protoc-gen-go v1.26.0
// protoc v3.15.8
// source: commonpb/common.proto
package commonpb
@@ -21,16 +21,18 @@ const (
)
type Empty struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Empty) Reset() {
*x = Empty{}
mi := &file_commonpb_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Empty) String() string {
@@ -41,7 +43,7 @@ func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[0]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -58,20 +60,23 @@ func (*Empty) Descriptor() ([]byte, []int) {
// Request - Common fields used in all gRPC requests
type Request struct {
state protoimpl.MessageState `protogen:"open.v1"`
Async bool `protobuf:"varint,1,opt,name=Async,proto3" json:"Async,omitempty"`
Timeout int64 `protobuf:"varint,2,opt,name=Timeout,proto3" json:"Timeout,omitempty"`
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
SessionID string `protobuf:"bytes,9,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Async bool `protobuf:"varint,1,opt,name=Async,proto3" json:"Async,omitempty"`
Timeout int64 `protobuf:"varint,2,opt,name=Timeout,proto3" json:"Timeout,omitempty"`
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
SessionID string `protobuf:"bytes,9,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
}
func (x *Request) Reset() {
*x = Request{}
mi := &file_commonpb_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Request) String() string {
@@ -82,7 +87,7 @@ func (*Request) ProtoMessage() {}
func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[1]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -126,24 +131,26 @@ func (x *Request) GetSessionID() string {
}
// Response - Common fields used in all gRPC responses. Note that the Err field
//
// only used when the implant needs to return an error to the server.
// Client<->Server comms should use normal gRPC error handling.
// only used when the implant needs to return an error to the server.
// Client<->Server comms should use normal gRPC error handling.
type Response struct {
state protoimpl.MessageState `protogen:"open.v1"`
Err string `protobuf:"bytes,1,opt,name=Err,proto3" json:"Err,omitempty"`
Async bool `protobuf:"varint,2,opt,name=Async,proto3" json:"Async,omitempty"`
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
TaskID string `protobuf:"bytes,9,opt,name=TaskID,proto3" json:"TaskID,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Err string `protobuf:"bytes,1,opt,name=Err,proto3" json:"Err,omitempty"`
Async bool `protobuf:"varint,2,opt,name=Async,proto3" json:"Async,omitempty"`
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
TaskID string `protobuf:"bytes,9,opt,name=TaskID,proto3" json:"TaskID,omitempty"`
}
func (x *Response) Reset() {
*x = Response{}
mi := &file_commonpb_common_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Response) String() string {
@@ -154,7 +161,7 @@ func (*Response) ProtoMessage() {}
func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[2]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -199,18 +206,21 @@ func (x *Response) GetTaskID() string {
// File - A basic file data type
type File struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
}
func (x *File) Reset() {
*x = File{}
mi := &file_commonpb_common_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *File) String() string {
@@ -221,7 +231,7 @@ func (*File) ProtoMessage() {}
func (x *File) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[3]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -252,23 +262,26 @@ func (x *File) GetData() []byte {
// Process - A basic process data type
type Process struct {
state protoimpl.MessageState `protogen:"open.v1"`
Pid int32 `protobuf:"varint,1,opt,name=Pid,proto3" json:"Pid,omitempty"`
Ppid int32 `protobuf:"varint,2,opt,name=Ppid,proto3" json:"Ppid,omitempty"`
Executable string `protobuf:"bytes,3,opt,name=Executable,proto3" json:"Executable,omitempty"`
Owner string `protobuf:"bytes,4,opt,name=Owner,proto3" json:"Owner,omitempty"`
Architecture string `protobuf:"bytes,7,opt,name=Architecture,proto3" json:"Architecture,omitempty"`
SessionID int32 `protobuf:"varint,5,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
CmdLine []string `protobuf:"bytes,6,rep,name=CmdLine,proto3" json:"CmdLine,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Pid int32 `protobuf:"varint,1,opt,name=Pid,proto3" json:"Pid,omitempty"`
Ppid int32 `protobuf:"varint,2,opt,name=Ppid,proto3" json:"Ppid,omitempty"`
Executable string `protobuf:"bytes,3,opt,name=Executable,proto3" json:"Executable,omitempty"`
Owner string `protobuf:"bytes,4,opt,name=Owner,proto3" json:"Owner,omitempty"`
Architecture string `protobuf:"bytes,7,opt,name=Architecture,proto3" json:"Architecture,omitempty"`
SessionID int32 `protobuf:"varint,5,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
CmdLine []string `protobuf:"bytes,6,rep,name=CmdLine,proto3" json:"CmdLine,omitempty"`
}
func (x *Process) Reset() {
*x = Process{}
mi := &file_commonpb_common_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Process) String() string {
@@ -279,7 +292,7 @@ func (*Process) ProtoMessage() {}
func (x *Process) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[4]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -345,18 +358,21 @@ func (x *Process) GetCmdLine() []string {
// EnvVar - Environment variable K/V
type EnvVar struct {
state protoimpl.MessageState `protogen:"open.v1"`
Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
}
func (x *EnvVar) Reset() {
*x = EnvVar{}
mi := &file_commonpb_common_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_commonpb_common_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EnvVar) String() string {
@@ -367,7 +383,7 @@ func (*EnvVar) ProtoMessage() {}
func (x *EnvVar) ProtoReflect() protoreflect.Message {
mi := &file_commonpb_common_proto_msgTypes[5]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -453,7 +469,7 @@ func file_commonpb_common_proto_rawDescGZIP() []byte {
}
var file_commonpb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_commonpb_common_proto_goTypes = []any{
var file_commonpb_common_proto_goTypes = []interface{}{
(*Empty)(nil), // 0: commonpb.Empty
(*Request)(nil), // 1: commonpb.Request
(*Response)(nil), // 2: commonpb.Response
@@ -474,6 +490,80 @@ func file_commonpb_common_proto_init() {
if File_commonpb_common_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_commonpb_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_commonpb_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Request); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_commonpb_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Response); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_commonpb_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*File); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_commonpb_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Process); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_commonpb_common_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EnvVar); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
+37 -18
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.3
// protoc v5.29.3
// protoc-gen-go v1.26.0
// protoc v3.15.8
// source: dnspb/dns.proto
package dnspb
@@ -87,27 +87,32 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
return file_dnspb_dns_proto_rawDescGZIP(), []int{0}
}
// NOTE: DNS is very space sensitive so certain fields are re-purposed
// depending on the DNSMessageType as noted below:
//
// [Type TOTP]: ID field is used for the TOTP code
//NOTE: DNS is very space sensitive so certain fields are re-purposed
//depending on the DNSMessageType as noted below:
//
//[Type TOTP]: ID field is used for the TOTP code
//
type DNSMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type DNSMessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=dnspb.DNSMessageType" json:"Type,omitempty"`
ID uint32 `protobuf:"varint,2,opt,name=ID,proto3" json:"ID,omitempty"` // 8 bit message id + 24 bit dns session ID
Start uint32 `protobuf:"varint,3,opt,name=Start,proto3" json:"Start,omitempty"` // Bytes start at
Stop uint32 `protobuf:"varint,4,opt,name=Stop,proto3" json:"Stop,omitempty"` // Bytes stop at
Size uint32 `protobuf:"varint,5,opt,name=Size,proto3" json:"Size,omitempty"` // Total size
Data []byte `protobuf:"bytes,6,opt,name=Data,proto3" json:"Data,omitempty"` // Actual data
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type DNSMessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=dnspb.DNSMessageType" json:"Type,omitempty"`
ID uint32 `protobuf:"varint,2,opt,name=ID,proto3" json:"ID,omitempty"` // 8 bit message id + 24 bit dns session ID
Start uint32 `protobuf:"varint,3,opt,name=Start,proto3" json:"Start,omitempty"` // Bytes start at
Stop uint32 `protobuf:"varint,4,opt,name=Stop,proto3" json:"Stop,omitempty"` // Bytes stop at
Size uint32 `protobuf:"varint,5,opt,name=Size,proto3" json:"Size,omitempty"` // Total size
Data []byte `protobuf:"bytes,6,opt,name=Data,proto3" json:"Data,omitempty"` // Actual data
}
func (x *DNSMessage) Reset() {
*x = DNSMessage{}
mi := &file_dnspb_dns_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
if protoimpl.UnsafeEnabled {
mi := &file_dnspb_dns_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DNSMessage) String() string {
@@ -118,7 +123,7 @@ func (*DNSMessage) ProtoMessage() {}
func (x *DNSMessage) ProtoReflect() protoreflect.Message {
mi := &file_dnspb_dns_proto_msgTypes[0]
if x != nil {
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -218,7 +223,7 @@ func file_dnspb_dns_proto_rawDescGZIP() []byte {
var file_dnspb_dns_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_dnspb_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_dnspb_dns_proto_goTypes = []any{
var file_dnspb_dns_proto_goTypes = []interface{}{
(DNSMessageType)(0), // 0: dnspb.DNSMessageType
(*DNSMessage)(nil), // 1: dnspb.DNSMessage
}
@@ -236,6 +241,20 @@ func file_dnspb_dns_proto_init() {
if File_dnspb_dns_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_dnspb_dns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DNSMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
+3 -3
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.3
// protoc v5.29.3
// protoc-gen-go v1.26.0
// protoc v3.15.8
// source: rpcpb/services.proto
package rpcpb
@@ -724,7 +724,7 @@ var file_rpcpb_services_proto_rawDesc = []byte{
0x70, 0x63, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_rpcpb_services_proto_goTypes = []any{
var file_rpcpb_services_proto_goTypes = []interface{}{
(*commonpb.Empty)(nil), // 0: commonpb.Empty
(*clientpb.ClientLogData)(nil), // 1: clientpb.ClientLogData
(*sliverpb.KillReq)(nil), // 2: sliverpb.KillReq
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+3
View File
@@ -515,6 +515,9 @@ message ExecuteReq {
bool Output = 3;
string Stdout = 4;
string Stderr = 5;
bool EnvInheritance = 6;
map<string, string> Env = 7;
uint32 PPid = 10;
commonpb.Request Request = 9;
+3 -3
View File
@@ -277,7 +277,7 @@ func SetupGoPath(goPathSrc string, includeDNS bool) error {
}
sliverpbGoSrc = xorPBRawBytes(sliverpbGoSrc)
sliverpbGoSrc = stripSliverpb(sliverpbGoSrc)
sliverpbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "sliverpb")
sliverpbDir := filepath.Join(goPathSrc, "protobuf", "sliverpb")
setupLog.Infof("Creating sliverpb directory: %s", sliverpbDir)
os.MkdirAll(sliverpbDir, 0700)
os.WriteFile(filepath.Join(sliverpbDir, "sliver.pb.go"), sliverpbGoSrc, 0600)
@@ -290,7 +290,7 @@ func SetupGoPath(goPathSrc string, includeDNS bool) error {
return err
}
commonpbSrc = xorPBRawBytes(commonpbSrc)
commonpbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "commonpb")
commonpbDir := filepath.Join(goPathSrc, "protobuf", "commonpb")
os.MkdirAll(commonpbDir, 0700)
os.WriteFile(filepath.Join(commonpbDir, "common.pb.go"), commonpbSrc, 0600)
@@ -302,7 +302,7 @@ func SetupGoPath(goPathSrc string, includeDNS bool) error {
return err
}
dnspbSrc = xorPBRawBytes(dnspbSrc)
dnspbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "dnspb")
dnspbDir := filepath.Join(goPathSrc, "protobuf", "dnspb")
os.MkdirAll(dnspbDir, 0700)
os.WriteFile(filepath.Join(dnspbDir, "dns.pb.go"), dnspbSrc, 0600)
}
+5 -1
View File
@@ -69,6 +69,7 @@ var (
const (
DefaultMaxBodyLength = 2 * 1024 * 1024 * 1024 // 2Gb
DefaultMaxUnauthBodyLength = 8 * 1024 * 1024 // 8Mb
DefaultHTTPTimeout = time.Minute
DefaultLongPollTimeout = time.Second
DefaultLongPollJitter = time.Second
@@ -564,7 +565,10 @@ func (s *SliverHTTPC2) anonymousHandler(resp http.ResponseWriter, req *http.Requ
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request, encoder sliverEncoders.Encoder) {
httpLog.Debug("Start http session request")
body, err := io.ReadAll(req.Body)
body, err := io.ReadAll(&io.LimitedReader{
R: req.Body,
N: int64(DefaultMaxUnauthBodyLength),
})
if err != nil {
httpLog.Errorf("Failed to read body %s", err)
s.defaultHandler(resp, req)
+10 -1
View File
@@ -28,6 +28,7 @@ import (
"github.com/spf13/cobra"
clientcli "github.com/bishopfox/sliver/client/cli"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/c2"
"github.com/bishopfox/sliver/server/certs"
@@ -72,6 +73,8 @@ func initConsoleLogging(appDir string) *os.File {
}
func init() {
rootCmd.Flags().String(clientcli.RCFlagName, "", "path to rc script file")
// Unpack
unpackCmd.Flags().BoolP(forceFlagStr, "f", false, "Force unpack and overwrite")
rootCmd.AddCommand(unpackCmd)
@@ -147,8 +150,14 @@ var rootCmd = &cobra.Command{
if serverConfig.DaemonMode {
daemon.Start(daemon.BlankHost, daemon.BlankPort, serverConfig.DaemonConfig.Tailscale)
} else {
rcScript, err := clientcli.ReadRCScript(cmd)
if err != nil {
fmt.Printf("Failed to read rc script: %s\n", err)
return
}
os.Args = os.Args[:1] // Hide cli from grumble console
console.Start()
console.Start(rcScript)
}
},
}
+71 -2
View File
@@ -26,6 +26,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
@@ -47,6 +48,13 @@ var (
ErrInvalidDialect = errors.New("invalid SQL Dialect")
databaseConfigLog = log.NamedLogger("config", "database")
defaultSQLitePragmas = map[string]string{
"journal_mode": "WAL", // reduce writer blocking, better concurrency
"busy_timeout": "5000", // wait for locks instead of failing fast (ms)
"synchronous": "NORMAL", // faster WAL syncs while retaining durability
"temp_store": "MEMORY", // keep temp structures off disk for quicker startup
}
)
// GetDatabaseConfigPath - File path to config.json
@@ -66,7 +74,8 @@ type DatabaseConfig struct {
Host string `json:"host"`
Port uint16 `json:"port"`
Params map[string]string `json:"params"`
Params map[string]string `json:"params"`
Pragmas map[string]string `json:"pragmas"`
MaxIdleConns int `json:"max_idle_conns"`
MaxOpenConns int `json:"max_open_conns"`
@@ -80,7 +89,7 @@ func (c *DatabaseConfig) DSN() (string, error) {
switch c.Dialect {
case Sqlite:
filePath := filepath.Join(assets.GetRootAppDir(), "sliver.db")
params := encodeParams(c.Params)
params := encodeSQLiteParams(c.Params, c.Pragmas)
return fmt.Sprintf("file:%s?%s", filePath, params), nil
case MySQL:
user := url.QueryEscape(c.Username)
@@ -112,6 +121,44 @@ func encodeParams(rawParams map[string]string) string {
return params.Encode()
}
func encodeSQLiteParams(rawParams map[string]string, pragmas map[string]string) string {
if rawParams == nil {
rawParams = map[string]string{}
}
params := url.Values{}
// Preserve any user-provided parameters first.
for key, value := range rawParams {
params.Add(key, value)
}
// Apply safer defaults only when the user has not set any custom pragma.
if _, ok := rawParams["_pragma"]; !ok {
selectedPragmas := pragmas
if len(selectedPragmas) == 0 {
selectedPragmas = defaultSQLitePragmas
}
keys := make([]string, 0, len(selectedPragmas))
for key := range selectedPragmas {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
value := selectedPragmas[key]
params.Add("_pragma", fmt.Sprintf("%s(%s)", key, value))
}
}
// Encourage shared cache to play nicer with WAL and multiple connections.
if _, ok := rawParams["cache"]; !ok {
params.Add("cache", "shared")
}
return params.Encode()
}
// Save - Save config file to disk
func (c *DatabaseConfig) Save() error {
configPath := GetDatabaseConfigPath()
@@ -161,6 +208,8 @@ func GetDatabaseConfig() *DatabaseConfig {
config.MaxOpenConns = 1
}
ensureSQLiteDefaults(config)
err := config.Save() // This updates the config with any missing fields
if err != nil {
databaseConfigLog.Errorf("Failed to save default config %s", err)
@@ -171,9 +220,29 @@ func GetDatabaseConfig() *DatabaseConfig {
func getDefaultDatabaseConfig() *DatabaseConfig {
return &DatabaseConfig{
Dialect: Sqlite,
Pragmas: defaultSQLitePragmas,
MaxIdleConns: 10,
MaxOpenConns: 100,
LogLevel: "warn",
}
}
func ensureSQLiteDefaults(c *DatabaseConfig) {
if c.Dialect != Sqlite {
return
}
if c.Params == nil {
c.Params = map[string]string{}
}
if _, ok := c.Params["_pragma"]; ok {
// User provided explicit pragmas; leave Pragmas untouched to avoid confusion.
return
}
if len(c.Pragmas) == 0 {
c.Pragmas = defaultSQLitePragmas
}
if _, ok := c.Params["cache"]; !ok {
c.Params["cache"] = "shared"
}
}
+94 -2
View File
@@ -28,12 +28,15 @@ import (
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/spf13/cobra"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/console/forms"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
@@ -172,14 +175,54 @@ func NewOperatorConfig(operatorName string, lhost string, lport uint16, permissi
return json.Marshal(config)
}
func kickOperatorCmd(cmd *cobra.Command, _ []string) {
func kickOperatorCmd(cmd *cobra.Command, args []string) {
operator, _ := cmd.Flags().GetString("name")
if shouldPromptKickOperator(cmd, args) {
names, err := operatorNames()
if err != nil {
fmt.Printf(Warn+"Failed to list operators: %v\n", err)
return
}
if len(names) == 0 {
fmt.Printf(Warn + "No operators found.\n")
return
}
if err := forms.SelectOperator("Select operator to kick", names, &operator); err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
}
fmt.Printf(Warn+"Operator selection failed: %v\n", err)
return
}
if err := cmd.Flags().Set("name", operator); err != nil {
fmt.Printf(Warn+"Failed to set operator name: %v\n", err)
return
}
}
operator = strings.TrimSpace(operator)
if operator == "" {
fmt.Printf(Warn + "Operator name required (use --name).\n")
return
}
exists, err := operatorExists(operator)
if err != nil {
fmt.Printf(Warn+"Failed to lookup operator %s: %v\n", operator, err)
return
}
if !exists {
fmt.Printf(Warn+"Operator %s does not exist.\n", operator)
return
}
fmt.Printf(Info+"Removing auth token(s) for %s, please wait ... \n", operator)
err := db.Session().Where(&models.Operator{
err = db.Session().Where(&models.Operator{
Name: operator,
}).Delete(&models.Operator{}).Error
if err != nil {
fmt.Printf(Warn+"Failed to remove operator %s: %v\n", operator, err)
return
}
transport.ClearTokenCache()
@@ -192,6 +235,55 @@ func kickOperatorCmd(cmd *cobra.Command, _ []string) {
fmt.Printf(Info+"Operator %s has been kicked out.\n", operator)
}
func shouldPromptKickOperator(cmd *cobra.Command, args []string) bool {
if len(args) != 0 {
return false
}
return cmd.Flags().NFlag() == 0
}
func operatorNames() ([]string, error) {
operators, err := db.OperatorAll()
if err != nil {
return nil, err
}
names := make([]string, 0, len(operators))
seen := make(map[string]struct{}, len(operators))
for _, operator := range operators {
if operator == nil {
continue
}
name := strings.TrimSpace(operator.Name)
if name == "" {
continue
}
if _, ok := seen[name]; ok {
continue
}
seen[name] = struct{}{}
names = append(names, name)
}
sort.Strings(names)
return names, nil
}
func operatorExists(name string) (bool, error) {
if strings.TrimSpace(name) == "" {
return false, nil
}
err := db.Session().Where(&models.Operator{
Name: name,
}).First(&models.Operator{}).Error
if err == nil {
return true, nil
}
if errors.Is(err, db.ErrRecordNotFound) {
return false, nil
}
return false, err
}
func startMultiplayerModeCmd(cmd *cobra.Command, _ []string) {
lhost, _ := cmd.Flags().GetString("lhost")
lport, _ := cmd.Flags().GetUint16("lport")
+2 -2
View File
@@ -38,7 +38,7 @@ import (
)
// Start - Starts the server console
func Start() {
func Start(rcScript string) {
_, ln, _ := transport.LocalListener()
ctxDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return ln.Dial()
@@ -57,7 +57,7 @@ func Start() {
defer conn.Close()
localRPC := rpcpb.NewSliverRPCClient(conn)
con := console.NewConsole(false)
console.StartClient(con, localRPC, command.ServerCommands(con, serverOnlyCmds), command.SliverCommands(con), true)
console.StartClient(con, localRPC, command.ServerCommands(con, serverOnlyCmds), command.SliverCommands(con), true, rcScript)
con.App.Start()
}
+74
View File
@@ -0,0 +1,74 @@
package forms
import (
"errors"
"strings"
"github.com/charmbracelet/huh"
)
const defaultSelectHeight = 10
// ErrUserAborted exposes the huh user-abort sentinel for callers.
var ErrUserAborted = huh.ErrUserAborted
// SelectOperator prompts for a single operator selection.
func SelectOperator(title string, operators []string, value *string) error {
if value == nil {
return errors.New("operator selection value is required")
}
if len(operators) == 0 {
return errors.New("operator selection options are required")
}
ensureSelectedValue(operators, value)
field := huh.NewSelect[string]().
Title(title).
Options(makeStringOptions(operators)...).
Height(listHeight(len(operators))).
Value(value).
Validate(func(val string) error {
if strings.TrimSpace(val) == "" {
return errors.New("selection required")
}
return nil
})
form := huh.NewForm(huh.NewGroup(field))
return form.Run()
}
func ensureSelectedValue(options []string, value *string) {
if value == nil || len(options) == 0 {
return
}
if strings.TrimSpace(*value) == "" {
*value = options[0]
return
}
for _, option := range options {
if option == *value {
return
}
}
*value = options[0]
}
func makeStringOptions(options []string) []huh.Option[string] {
converted := make([]huh.Option[string], 0, len(options))
for _, option := range options {
converted = append(converted, huh.NewOption(option, option))
}
return converted
}
func listHeight(count int) int {
if count < 1 {
return 1
}
if count > defaultSelectHeight {
return defaultSelectHeight
}
return count
}
+97 -8
View File
@@ -6,9 +6,11 @@ import (
)
var (
Rtunnels map[uint64]*RTunnel = make(map[uint64]*RTunnel)
mutex sync.RWMutex
pending sync.Map
Rtunnels map[uint64]*RTunnel = make(map[uint64]*RTunnel)
mutex sync.RWMutex
pending map[string]map[string]int
listeners map[string]map[uint32]string
pendingMu sync.Mutex
)
// RTunnel - Duplex byte read/write
@@ -97,20 +99,107 @@ func RemoveRTunnel(ID uint64) {
}
func AddPending(sessionID string, connStr string) {
pending.Store(sessionID, connStr)
pendingMu.Lock()
defer pendingMu.Unlock()
if pending == nil {
pending = make(map[string]map[string]int)
}
addrMap := pending[sessionID]
if addrMap == nil {
addrMap = make(map[string]int)
pending[sessionID] = addrMap
}
addrMap[connStr]++
}
func DeletePending(sessionID string) {
pending.Delete(sessionID)
func DeletePending(sessionID string, connStr string) {
pendingMu.Lock()
defer pendingMu.Unlock()
if pending == nil {
return
}
if addrMap, ok := pending[sessionID]; ok {
if count, ok := addrMap[connStr]; ok {
if count <= 1 {
delete(addrMap, connStr)
} else {
addrMap[connStr] = count - 1
}
}
if len(addrMap) == 0 {
delete(pending, sessionID)
}
}
}
func Check(sessionID string, connStr string) bool {
if val, ok := pending.Load(sessionID); ok {
return val == connStr
pendingMu.Lock()
defer pendingMu.Unlock()
if pending == nil {
return false
}
if addrMap, ok := pending[sessionID]; ok {
return addrMap[connStr] > 0
}
return false
}
func TrackListener(sessionID string, listenerID uint32, connStr string) {
pendingMu.Lock()
defer pendingMu.Unlock()
if listeners == nil {
listeners = make(map[string]map[uint32]string)
}
if pending == nil {
pending = make(map[string]map[string]int)
}
lm := listeners[sessionID]
if lm == nil {
lm = make(map[uint32]string)
listeners[sessionID] = lm
}
lm[listenerID] = connStr
addrMap := pending[sessionID]
if addrMap == nil {
addrMap = make(map[string]int)
pending[sessionID] = addrMap
}
addrMap[connStr]++
}
func UntrackListener(sessionID string, listenerID uint32) bool {
pendingMu.Lock()
defer pendingMu.Unlock()
if listeners == nil || pending == nil {
return false
}
lm, ok := listeners[sessionID]
if !ok {
return false
}
connStr, ok := lm[listenerID]
if !ok {
return false
}
delete(lm, listenerID)
if len(lm) == 0 {
delete(listeners, sessionID)
}
if addrMap, ok := pending[sessionID]; ok {
if count, ok := addrMap[connStr]; ok {
if count <= 1 {
delete(addrMap, connStr)
} else {
addrMap[connStr] = count - 1
}
}
if len(addrMap) == 0 {
delete(pending, sessionID)
}
}
return true
}
// func removeAndCloseAllRTunnels() {
// mutex.Lock()
// defer mutex.Unlock()
Loaded 100 of 770 files, more files were not shown because too many files have changed in this diff. Show more