mirror of
https://github.com/BishopFox/sliver
synced 2026-06-08 10:29:05 +00:00
151 lines
2.6 KiB
Go
151 lines
2.6 KiB
Go
package shell
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/bishopfox/sliver/client/core"
|
|
)
|
|
|
|
type shellManager struct {
|
|
mu sync.RWMutex
|
|
nextID int
|
|
shells map[int]*managedShell
|
|
}
|
|
|
|
func newShellManager() *shellManager {
|
|
return &shellManager{
|
|
nextID: 1,
|
|
shells: map[int]*managedShell{},
|
|
}
|
|
}
|
|
|
|
var shells = newShellManager()
|
|
|
|
type shellState string
|
|
|
|
const (
|
|
shellStateAttached shellState = "attached"
|
|
shellStateDetached shellState = "detached"
|
|
shellStateClosing shellState = "closing"
|
|
)
|
|
|
|
type managedShell struct {
|
|
ID int
|
|
SessionID string
|
|
SessionName string
|
|
Path string
|
|
Pid uint32
|
|
TunnelID uint64
|
|
EnablePTY bool
|
|
CreatedAt time.Time
|
|
|
|
mu sync.RWMutex
|
|
state shellState
|
|
tunnel *core.TunnelIO
|
|
output *swapWriter
|
|
readerWG sync.WaitGroup
|
|
}
|
|
|
|
func (s *managedShell) State() shellState {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.state
|
|
}
|
|
|
|
func (s *managedShell) setState(state shellState) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.state = state
|
|
}
|
|
|
|
func (s *managedShell) Tunnel() *core.TunnelIO {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.tunnel
|
|
}
|
|
|
|
func (s *managedShell) SetOutput(w io.Writer) {
|
|
s.output.Set(w)
|
|
}
|
|
|
|
func (s *managedShell) startReader(remove func()) {
|
|
s.readerWG.Add(1)
|
|
go func() {
|
|
defer s.readerWG.Done()
|
|
_, err := io.Copy(s.output, s.tunnel)
|
|
log.Printf("Shell tunnel reader (id=%d tunnel=%d) exited: %v", s.ID, s.TunnelID, err)
|
|
remove()
|
|
}()
|
|
}
|
|
|
|
// swapWriter is an io.Writer whose underlying destination can be swapped at runtime.
|
|
// This is used so detached shells can keep draining output without clobbering the REPL.
|
|
type swapWriter struct {
|
|
mu sync.RWMutex
|
|
w io.Writer
|
|
}
|
|
|
|
func newSwapWriter(w io.Writer) *swapWriter {
|
|
return &swapWriter{w: w}
|
|
}
|
|
|
|
func (s *swapWriter) Set(w io.Writer) {
|
|
s.mu.Lock()
|
|
s.w = w
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *swapWriter) Write(p []byte) (int, error) {
|
|
s.mu.RLock()
|
|
w := s.w
|
|
s.mu.RUnlock()
|
|
if w == nil {
|
|
return len(p), nil
|
|
}
|
|
return w.Write(p)
|
|
}
|
|
|
|
func (m *shellManager) Add(sh *managedShell) int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
id := m.nextID
|
|
m.nextID++
|
|
|
|
sh.ID = id
|
|
m.shells[id] = sh
|
|
|
|
return id
|
|
}
|
|
|
|
func (m *shellManager) Remove(id int) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.shells, id)
|
|
}
|
|
|
|
func (m *shellManager) Get(id int) (*managedShell, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
sh, ok := m.shells[id]
|
|
return sh, ok
|
|
}
|
|
|
|
func (m *shellManager) List() []*managedShell {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
results := make([]*managedShell, 0, len(m.shells))
|
|
for _, sh := range m.shells {
|
|
results = append(results, sh)
|
|
}
|
|
sort.Slice(results, func(i, j int) bool {
|
|
return results[i].ID < results[j].ID
|
|
})
|
|
return results
|
|
}
|