Files
2023-02-16 13:34:15 -08:00

730 lines
22 KiB
Go

package tasks
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"io/ioutil"
"os"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
"github.com/bishopfox/sliver/client/command/extensions"
"github.com/bishopfox/sliver/client/command/filesystem"
"github.com/bishopfox/sliver/client/command/network"
"github.com/bishopfox/sliver/client/command/privilege"
"github.com/bishopfox/sliver/client/command/processes"
"github.com/bishopfox/sliver/client/command/registry"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/desertbit/grumble"
"github.com/jedib0t/go-pretty/v6/table"
"google.golang.org/protobuf/proto"
)
// TasksFetchCmd - Manage beacon tasks
func TasksFetchCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
beacon := con.ActiveTarget.GetBeaconInteractive()
if beacon == nil {
return
}
beaconTasks, err := con.Rpc.GetBeaconTasks(context.Background(), &clientpb.Beacon{ID: beacon.ID})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
tasks := beaconTasks.Tasks
if len(tasks) == 0 {
con.PrintErrorf("No tasks for beacon\n")
return
}
idArg := ctx.Args.String("id")
if idArg != "" {
tasks = filterTasksByID(idArg, tasks)
if len(tasks) == 0 {
con.PrintErrorf("No beacon task found with id %s\n", idArg)
return
}
}
filter := ctx.Flags.String("filter")
if filter != "" {
tasks = filterTasksByTaskType(filter, tasks)
if len(tasks) == 0 {
con.PrintErrorf("No beacon tasks with filter type '%s'\n", filter)
return
}
}
var task *clientpb.BeaconTask
if 1 < len(tasks) {
task, err = SelectBeaconTask(tasks)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Printf(console.UpN+console.Clearln, 1)
} else {
task = tasks[0]
}
task, err = con.Rpc.GetBeaconTaskContent(context.Background(), &clientpb.BeaconTask{ID: task.ID})
if err != nil {
con.PrintErrorf("Failed to fetch task content: %s\n", err)
return
}
PrintTask(task, con)
}
func filterTasksByID(taskID string, tasks []*clientpb.BeaconTask) []*clientpb.BeaconTask {
filteredTasks := []*clientpb.BeaconTask{}
for _, task := range tasks {
if strings.HasPrefix(task.ID, strings.ToLower(taskID)) {
filteredTasks = append(filteredTasks, task)
}
}
return filteredTasks
}
func filterTasksByTaskType(taskType string, tasks []*clientpb.BeaconTask) []*clientpb.BeaconTask {
filteredTasks := []*clientpb.BeaconTask{}
for _, task := range tasks {
if strings.HasPrefix(strings.ToLower(task.Description), strings.ToLower(taskType)) {
filteredTasks = append(filteredTasks, task)
}
}
return filteredTasks
}
// PrintTask - Print the details of a beacon task
func PrintTask(task *clientpb.BeaconTask, con *console.SliverConsoleClient) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableWithBordersStyle(con))
tw.AppendRow(table.Row{console.Bold + "Beacon Task" + console.Normal, task.ID})
tw.AppendSeparator()
tw.AppendRow(table.Row{"State", emojiState(task.State) + " " + prettyState(strings.Title(task.State))})
tw.AppendRow(table.Row{"Description", task.Description})
tw.AppendRow(table.Row{"Created", time.Unix(task.CreatedAt, 0).Format(time.RFC1123)})
if !time.Unix(task.SentAt, 0).IsZero() {
tw.AppendRow(table.Row{"Sent", time.Unix(task.SentAt, 0).Format(time.RFC1123)})
}
if !time.Unix(task.CompletedAt, 0).IsZero() {
tw.AppendRow(table.Row{"Completed", time.Unix(task.CompletedAt, 0).Format(time.RFC1123)})
}
tw.AppendRow(table.Row{"Request Size", util.ByteCountBinary(int64(len(task.Request)))})
if !time.Unix(task.CompletedAt, 0).IsZero() {
tw.AppendRow(table.Row{"Response Size", util.ByteCountBinary(int64(len(task.Response)))})
}
tw.AppendSeparator()
con.Printf("%s\n", tw.Render())
if !time.Unix(task.CompletedAt, 0).IsZero() {
con.Println()
if 0 < len(task.Response) {
renderTaskResponse(task, con)
} else {
con.PrintInfof("No task response\n")
}
}
}
func emojiState(state string) string {
switch strings.ToLower(state) {
case "completed":
return "✅"
case "pending":
return "⏳"
case "failed":
return "❌"
case "canceled":
return "🚫"
default:
return "❓"
}
}
// Decode and render message specific content
func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleClient) {
reqEnvelope := &sliverpb.Envelope{}
proto.Unmarshal(task.Request, reqEnvelope)
switch reqEnvelope.Type {
// ---------------------
// Environment commands
// ---------------------
case sliverpb.MsgEnvInfo:
envInfo := &sliverpb.EnvInfo{}
err := proto.Unmarshal(task.Request, envInfo)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
environment.PrintGetEnvInfo(envInfo, con)
case sliverpb.MsgSetEnvReq:
setEnvReq := &sliverpb.SetEnvReq{}
err := proto.Unmarshal(task.Request, setEnvReq)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
setEnv := &sliverpb.SetEnv{}
err = proto.Unmarshal(task.Response, setEnv)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
environment.PrintSetEnvInfo(setEnvReq.Variable.Key, setEnvReq.Variable.Value, setEnv, con)
case sliverpb.MsgUnsetEnvReq:
unsetEnvReq := &sliverpb.UnsetEnvReq{}
err := proto.Unmarshal(task.Request, unsetEnvReq)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
unsetEnv := &sliverpb.UnsetEnv{}
err = proto.Unmarshal(task.Response, unsetEnv)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
environment.PrintUnsetEnvInfo(unsetEnvReq.Name, unsetEnv, con)
// ---------------------
// Call extension commands
// ---------------------
case sliverpb.MsgCallExtensionReq:
callExtension := &sliverpb.CallExtension{}
err := proto.Unmarshal(task.Response, callExtension)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
extensions.PrintExtOutput("", "", callExtension, con)
// ---------------------
// Exec commands
// ---------------------
case sliverpb.MsgInvokeExecuteAssemblyReq:
fallthrough
case sliverpb.MsgInvokeInProcExecuteAssemblyReq:
fallthrough
case sliverpb.MsgExecuteAssemblyReq:
execAssembly := &sliverpb.ExecuteAssembly{}
err := proto.Unmarshal(task.Response, execAssembly)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, _ := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
hostname := "hostname"
if beacon != nil {
hostname = beacon.Hostname
}
assemblyPath := ""
ctx := &grumble.Context{
Command: &grumble.Command{Name: "execute-assembly"},
Flags: grumble.FlagMap{
"save": &grumble.FlagMapItem{Value: false, IsDefault: true},
"loot": &grumble.FlagMapItem{Value: false, IsDefault: true},
"name": &grumble.FlagMapItem{Value: "", IsDefault: true},
},
}
exec.HandleExecuteAssemblyResponse(execAssembly, assemblyPath, hostname, ctx, con)
// execute-shellcode
case sliverpb.MsgTaskReq:
shellcodeExec := &sliverpb.Task{}
err := proto.Unmarshal(task.Response, shellcodeExec)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
exec.PrintExecuteShellcode(shellcodeExec, con)
case sliverpb.MsgExecuteReq:
execReq := &sliverpb.ExecuteReq{}
err := proto.Unmarshal(reqEnvelope.Data, execReq)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
execResult := &sliverpb.Execute{}
err = proto.Unmarshal(task.Response, execResult)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
ctx := &grumble.Context{
Flags: grumble.FlagMap{
"ignore-stderr": &grumble.FlagMapItem{Value: false},
"loot": &grumble.FlagMapItem{Value: false},
"stdout": &grumble.FlagMapItem{Value: ""},
"stderr": &grumble.FlagMapItem{Value: ""},
"output": &grumble.FlagMapItem{Value: true},
},
Args: grumble.ArgMap{
"command": &grumble.ArgMapItem{Value: execReq.Path},
"arguments": &grumble.ArgMapItem{Value: execReq.Args},
},
}
exec.PrintExecute(execResult, ctx, con)
case sliverpb.MsgSideloadReq:
sideload := &sliverpb.Sideload{}
err := proto.Unmarshal(task.Response, sideload)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, _ := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
hostname := "hostname"
if beacon != nil {
hostname = beacon.Hostname
}
ctx := &grumble.Context{
Command: &grumble.Command{Name: "sideload"},
Flags: grumble.FlagMap{
"save": &grumble.FlagMapItem{Value: false},
"loot": &grumble.FlagMapItem{Value: false},
},
}
exec.HandleSideloadResponse(sideload, "", hostname, ctx, con)
case sliverpb.MsgSpawnDllReq:
spawnDll := &sliverpb.SpawnDll{}
err := proto.Unmarshal(task.Response, spawnDll)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, _ := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
hostname := "hostname"
if beacon != nil {
hostname = beacon.Hostname
}
ctx := &grumble.Context{
Command: &grumble.Command{Name: "spawndll"},
Flags: grumble.FlagMap{
"save": &grumble.FlagMapItem{Value: false},
"loot": &grumble.FlagMapItem{Value: false},
},
}
exec.HandleSpawnDLLResponse(spawnDll, "", hostname, ctx, con)
case sliverpb.MsgSSHCommandReq:
sshCommand := &sliverpb.SSHCommand{}
err := proto.Unmarshal(task.Response, sshCommand)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
exec.PrintSSHCmd(sshCommand, con)
// ---------------------
// File system commands
// ---------------------
// Cat = download
case sliverpb.MsgCdReq:
pwd := &sliverpb.Pwd{}
err := proto.Unmarshal(task.Response, pwd)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
filesystem.PrintPwd(pwd, con)
case sliverpb.MsgDownload:
download := &sliverpb.Download{}
err := proto.Unmarshal(task.Response, download)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
taskResponseDownload(download, con)
case sliverpb.MsgLsReq:
ls := &sliverpb.Ls{}
err := proto.Unmarshal(task.Response, ls)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
flags := grumble.FlagMap{
"reverse": &grumble.FlagMapItem{Value: false},
"modified": &grumble.FlagMapItem{Value: false},
"size": &grumble.FlagMapItem{Value: false},
}
filesystem.PrintLs(ls, flags, con)
case sliverpb.MsgMvReq:
mv := &sliverpb.Mv{}
err := proto.Unmarshal(task.Response, mv)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
case sliverpb.MsgMkdirReq:
mkdir := &sliverpb.Mkdir{}
err := proto.Unmarshal(task.Response, mkdir)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
filesystem.PrintMkdir(mkdir, con)
case sliverpb.MsgPwdReq:
pwd := &sliverpb.Pwd{}
err := proto.Unmarshal(task.Response, pwd)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
filesystem.PrintPwd(pwd, con)
case sliverpb.MsgRmReq:
rm := &sliverpb.Rm{}
err := proto.Unmarshal(task.Response, rm)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
filesystem.PrintRm(rm, con)
case sliverpb.MsgUpload:
upload := &sliverpb.Upload{}
err := proto.Unmarshal(task.Response, upload)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
filesystem.PrintUpload(upload, con)
// ---------------------
// Network commands
// ---------------------
case sliverpb.MsgIfconfigReq:
ifconfig := &sliverpb.Ifconfig{}
err := proto.Unmarshal(task.Response, ifconfig)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
network.PrintIfconfig(ifconfig, true, con)
case sliverpb.MsgNetstatReq:
netstat := &sliverpb.Netstat{}
err := proto.Unmarshal(task.Response, netstat)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
if err != nil {
con.PrintErrorf("Failed to fetch beacon: %s\n", err)
return
}
network.PrintNetstat(netstat, beacon.PID, beacon.ActiveC2, false, con)
// ---------------------
// Privilege commands
// ---------------------
case sliverpb.MsgGetPrivsReq:
privs := &sliverpb.GetPrivs{}
err := proto.Unmarshal(task.Response, privs)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
if err != nil {
con.PrintErrorf("Failed to fetch beacon: %s\n", err)
return
}
privilege.PrintGetPrivs(privs, beacon.PID, con)
case sliverpb.MsgInvokeGetSystemReq:
getSystem := &sliverpb.GetSystem{}
err := proto.Unmarshal(task.Response, getSystem)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
privilege.PrintGetSystem(getSystem, con)
case sliverpb.MsgCurrentTokenOwnerReq:
cto := &sliverpb.CurrentTokenOwner{}
err := proto.Unmarshal(task.Response, cto)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
case sliverpb.MsgImpersonateReq:
impersonateReq := &sliverpb.ImpersonateReq{}
err := proto.Unmarshal(task.Response, impersonateReq)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
impersonate := &sliverpb.Impersonate{}
err = proto.Unmarshal(task.Response, impersonate)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
privilege.PrintImpersonate(impersonate, impersonateReq.Username, con)
case sliverpb.MsgMakeTokenReq:
makeTokenReq := &sliverpb.MakeTokenReq{}
err := proto.Unmarshal(task.Response, makeTokenReq)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
makeToken := &sliverpb.MakeToken{}
err = proto.Unmarshal(task.Response, makeToken)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
privilege.PrintMakeToken(makeToken, makeTokenReq.Domain, makeTokenReq.Username, con)
case sliverpb.MsgRunAsReq:
runAsReq := &sliverpb.RunAsReq{}
err := proto.Unmarshal(task.Response, runAsReq)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
runAs := &sliverpb.RunAs{}
err = proto.Unmarshal(task.Response, runAs)
if err != nil {
con.PrintErrorf("Failed to decode task request: %s\n", err)
return
}
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
if err != nil {
con.PrintErrorf("Failed to fetch beacon: %s\n", err)
return
}
privilege.PrintRunAs(runAs, runAsReq.ProcessName, runAsReq.Args, beacon.Name, con)
// ---------------------
// Processes commands
// ---------------------
case sliverpb.MsgProcessDumpReq:
dump := &sliverpb.ProcessDump{}
err := proto.Unmarshal(task.Response, dump)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
promptSaveToFile(dump.Data, con)
case sliverpb.MsgPsReq:
ps := &sliverpb.Ps{}
err := proto.Unmarshal(task.Response, ps)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
beacon, err := con.Rpc.GetBeacon(context.Background(), &clientpb.Beacon{ID: task.BeaconID})
if err != nil {
con.PrintErrorf("Failed to get beacon: %s\n", err)
return
}
ctx := &grumble.Context{
Flags: grumble.FlagMap{
"pid": &grumble.FlagMapItem{Value: -1},
"exe": &grumble.FlagMapItem{Value: ""},
"owner": &grumble.FlagMapItem{Value: ""},
"overflow": &grumble.FlagMapItem{Value: false},
"skip-pages": &grumble.FlagMapItem{Value: 0},
"print-cmdline": &grumble.FlagMapItem{Value: true},
"tree": &grumble.FlagMapItem{Value: false},
},
}
processes.PrintPS(beacon.OS, ps, true, ctx, con)
case sliverpb.MsgTerminateReq:
terminate := &sliverpb.Terminate{}
err := proto.Unmarshal(task.Response, terminate)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
processes.PrintTerminate(terminate, con)
// ---------------------
// Registry commands
// ---------------------
case sliverpb.MsgRegistryCreateKeyReq:
createKeyReq := &sliverpb.RegistryCreateKeyReq{}
err := proto.Unmarshal(task.Request, createKeyReq)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
createKey := &sliverpb.RegistryCreateKey{}
err = proto.Unmarshal(task.Response, createKey)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintCreateKey(createKey, createKeyReq.Path, createKeyReq.Key, con)
case sliverpb.MsgRegistryDeleteKeyReq:
deleteKeyReq := &sliverpb.RegistryDeleteKeyReq{}
err := proto.Unmarshal(task.Request, deleteKeyReq)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
deleteKey := &sliverpb.RegistryDeleteKey{}
err = proto.Unmarshal(task.Response, deleteKey)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintDeleteKey(deleteKey, deleteKeyReq.Path, deleteKeyReq.Key, con)
case sliverpb.MsgRegistryListValuesReq:
listValuesReq := &sliverpb.RegistryListValuesReq{}
err := proto.Unmarshal(task.Request, listValuesReq)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
regList := &sliverpb.RegistryValuesList{}
err = proto.Unmarshal(task.Response, regList)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintListValues(regList, listValuesReq.Hive, listValuesReq.Path, con)
case sliverpb.MsgRegistrySubKeysListReq:
listValuesReq := &sliverpb.RegistrySubKeyListReq{}
err := proto.Unmarshal(task.Request, listValuesReq)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
regList := &sliverpb.RegistrySubKeyList{}
err = proto.Unmarshal(task.Response, regList)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintListSubKeys(regList, listValuesReq.Hive, listValuesReq.Path, con)
case sliverpb.MsgRegistryReadReq:
regRead := &sliverpb.RegistryRead{}
err := proto.Unmarshal(task.Response, regRead)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintRegRead(regRead, con)
case sliverpb.MsgRegistryWriteReq:
regWrite := &sliverpb.RegistryWrite{}
err := proto.Unmarshal(task.Response, regWrite)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
registry.PrintRegWrite(regWrite, con)
// ---------------------
// Screenshot
// ---------------------
case sliverpb.MsgScreenshot:
screenshot := &sliverpb.Screenshot{}
err := proto.Unmarshal(task.Response, screenshot)
if err != nil {
con.PrintErrorf("Failed to decode task response: %s\n", err)
return
}
promptSaveToFile(screenshot.Data, con)
// ---------------------
// Default
// ---------------------
default:
con.PrintErrorf("Cannot render task response for msg type %v\n", reqEnvelope.Type)
}
}
func taskResponseDownload(download *sliverpb.Download, con *console.SliverConsoleClient) {
const (
dump = "Dump Contents"
saveTo = "Save to File ..."
)
action := saveTo
prompt := &survey.Select{
Message: "Choose an option:",
Options: []string{dump, saveTo},
}
err := survey.AskOne(prompt, &action, survey.WithValidator(survey.Required))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
switch action {
case dump:
con.Printf("%s\n", string(download.Data))
default:
promptSaveToFile(download.Data, con)
}
}
func promptSaveToFile(data []byte, con *console.SliverConsoleClient) {
saveTo := ""
saveToPrompt := &survey.Input{Message: "Save to: "}
err := survey.AskOne(saveToPrompt, &saveTo)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if _, err := os.Stat(saveTo); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: "Overwrite existing file?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
}
err = ioutil.WriteFile(saveTo, data, 0600)
if err != nil {
con.PrintErrorf("Failed to save file: %s\n", err)
return
}
con.PrintInfof("Wrote %d byte(s) to %s", len(data), saveTo)
}