Skip to content

Commit bd08364

Browse files
committed
Fix shell designation for linux
1 parent 702610f commit bd08364

File tree

5 files changed

+139
-115
lines changed

5 files changed

+139
-115
lines changed

internal/client/handlers/session.go

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"os"
77
"os/exec"
8+
"runtime"
89
"strings"
910

1011
"github.com/NHAS/reverse_ssh/internal"
@@ -13,9 +14,9 @@ import (
1314
"golang.org/x/crypto/ssh"
1415
)
1516

16-
//Session has a lot of 'function' in ssh. It can be used for shell, exec, subsystem, pty-req and more.
17-
//However these calls are done through requests, rather than opening a new channel
18-
//This callback just sorts out what the client wants to be doing
17+
// Session has a lot of 'function' in ssh. It can be used for shell, exec, subsystem, pty-req and more.
18+
// However these calls are done through requests, rather than opening a new channel
19+
// This callback just sorts out what the client wants to be doing
1920
func Session(user *internal.User, newChannel ssh.NewChannel, log logger.Logger) {
2021

2122
defer log.Info("Session disconnected")
@@ -65,48 +66,28 @@ func Session(user *internal.User, newChannel ssh.NewChannel, log logger.Logger)
6566
return
6667
}
6768

68-
//Set a path if no path is set to search
69-
if len(os.Getenv("PATH")) == 0 {
70-
os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin")
71-
}
72-
73-
cmd := exec.Command(parts[0], parts[1:]...)
74-
75-
stdout, err := cmd.StdoutPipe()
76-
if err != nil {
77-
fmt.Fprintf(connection, "%s", err.Error())
78-
return
79-
}
80-
defer stdout.Close()
81-
82-
cmd.Stderr = cmd.Stdout
83-
84-
stdin, err := cmd.StdinPipe()
85-
if err != nil {
86-
fmt.Fprintf(connection, "%s", err.Error())
87-
return
88-
}
89-
defer stdin.Close()
90-
91-
go io.Copy(stdin, connection)
92-
go io.Copy(connection, stdout)
93-
94-
err = cmd.Run()
95-
if err != nil {
96-
fmt.Fprintf(connection, "%s", err.Error())
69+
if user.Pty != nil {
70+
runCommandWithPty(parts[0], parts[1:], user, requests, log, connection)
9771
return
9872
}
73+
runCommand(parts[0], parts[1:], connection)
9974
}
10075

10176
return
10277
case "shell":
103-
// We only accept the default shell
104-
// (i.e. no command in the Payload)
105-
req.Reply(len(req.Payload) == 0, nil)
78+
//We accept the shell request
79+
req.Reply(true, nil)
80+
81+
var shellPath internal.ShellStruct
82+
err := ssh.Unmarshal(req.Payload, &shellPath)
83+
if err != nil || shellPath.Shell == "" {
10684

107-
//This blocks so will keep the channel from defer closing
108-
shell(user, connection, requests, log)
85+
//This blocks so will keep the channel from defer closing
86+
shell(user, connection, requests, log)
87+
return
88+
}
10989

90+
runCommandWithPty(shellPath.Shell, nil, user, requests, log, connection)
11091
return
11192
//Yes, this is here for a reason future me. Despite the RFC saying "Only one of shell,subsystem, exec can occur per channel" pty-req actually proceeds all of them
11293
case "pty-req":
@@ -130,3 +111,41 @@ func Session(user *internal.User, newChannel ssh.NewChannel, log logger.Logger)
130111
}
131112

132113
}
114+
115+
func runCommand(command string, args []string, connection ssh.Channel) {
116+
//Set a path if no path is set to search
117+
if len(os.Getenv("PATH")) == 0 {
118+
if runtime.GOOS != "windows" {
119+
os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin")
120+
} else {
121+
os.Setenv("PATH", "C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\v1.0\\")
122+
}
123+
}
124+
125+
cmd := exec.Command(command, args...)
126+
127+
stdout, err := cmd.StdoutPipe()
128+
if err != nil {
129+
fmt.Fprintf(connection, "%s", err.Error())
130+
return
131+
}
132+
defer stdout.Close()
133+
134+
cmd.Stderr = cmd.Stdout
135+
136+
stdin, err := cmd.StdinPipe()
137+
if err != nil {
138+
fmt.Fprintf(connection, "%s", err.Error())
139+
return
140+
}
141+
defer stdin.Close()
142+
143+
go io.Copy(stdin, connection)
144+
go io.Copy(connection, stdout)
145+
146+
err = cmd.Run()
147+
if err != nil {
148+
fmt.Fprintf(connection, "%s", err.Error())
149+
return
150+
}
151+
}

internal/client/handlers/shell.go

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,15 @@ func init() {
5555

5656
}
5757

58-
//This basically handles exactly like a SSH server would
59-
func shell(user *internal.User, connection ssh.Channel, requests <-chan *ssh.Request, log logger.Logger) {
58+
func runCommandWithPty(command string, args []string, user *internal.User, requests <-chan *ssh.Request, log logger.Logger, connection ssh.Channel) {
6059

61-
path := ""
62-
if len(shells) != 0 {
63-
path = shells[0]
60+
if user.Pty == nil {
61+
log.Error("Requested to run a command with a pty, but did not start a pty")
62+
return
6463
}
6564

6665
// Fire up a shell for this session
67-
shell := exec.Command(path)
66+
shell := exec.Command(command, args...)
6867
shell.Env = os.Environ()
6968

7069
close := func() {
@@ -84,33 +83,17 @@ func shell(user *internal.User, connection ssh.Channel, requests <-chan *ssh.Req
8483
var err error
8584
var shellIO io.ReadWriteCloser
8685

87-
if user.Pty != nil {
88-
shell.Env = append(shell.Env, "TERM="+user.Pty.Term)
89-
90-
log.Info("Creating pty...")
91-
shellIO, err = pty.StartWithSize(shell, &pty.Winsize{Cols: uint16(user.Pty.Columns), Rows: uint16(user.Pty.Rows)})
92-
if err != nil {
93-
log.Info("Could not start pty (%s)", err)
94-
close()
95-
return
96-
}
97-
} else {
98-
99-
stdinPipe, err := shell.StdinPipe()
100-
stdoutPipe, err := shell.StdoutPipe()
101-
shell.Stderr = shell.Stdout
102-
103-
shellIO = &ReaderWriteCloser{in: stdinPipe, out: stdoutPipe}
86+
shell.Env = append(shell.Env, "TERM="+user.Pty.Term)
10487

105-
err = shell.Start()
106-
if err != nil {
107-
log.Info("Could not start shell (%s)", err)
108-
close()
109-
return
110-
}
88+
log.Info("Creating pty...")
89+
shellIO, err = pty.StartWithSize(shell, &pty.Winsize{Cols: uint16(user.Pty.Columns), Rows: uint16(user.Pty.Rows)})
90+
if err != nil {
91+
log.Info("Could not start pty (%s)", err)
92+
close()
93+
return
11194
}
11295

113-
//pipe session to bash and visa-versa
96+
// pipe session to bash and visa-versa
11497
var once sync.Once
11598
go func() {
11699
io.Copy(connection, shellIO)
@@ -145,26 +128,21 @@ func shell(user *internal.User, connection ssh.Channel, requests <-chan *ssh.Req
145128

146129
defer once.Do(close)
147130
shell.Wait()
148-
149131
}
150132

151-
type ReaderWriteCloser struct {
152-
in io.WriteCloser
153-
out io.ReadCloser
154-
}
155-
156-
func (c *ReaderWriteCloser) Read(b []byte) (n int, err error) {
157-
return c.out.Read(b)
158-
}
133+
// This basically handles exactly like a SSH server would
134+
func shell(user *internal.User, connection ssh.Channel, requests <-chan *ssh.Request, log logger.Logger) {
159135

160-
func (c *ReaderWriteCloser) Write(b []byte) (n int, err error) {
161-
return c.in.Write(b)
162-
}
136+
path := ""
137+
if len(shells) != 0 {
138+
path = shells[0]
139+
}
163140

164-
func (c *ReaderWriteCloser) Close() error {
165-
c.in.Close()
141+
if user.Pty != nil {
142+
runCommandWithPty(path, nil, user, requests, log, connection)
143+
return
144+
}
166145

167-
err := c.out.Close()
146+
runCommand(path, nil, connection)
168147

169-
return err
170148
}

internal/client/handlers/shell_windows.go

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io"
99
"os"
1010
"os/exec"
11+
"strings"
1112
"syscall"
1213

1314
"github.com/ActiveState/termtest/conpty"
@@ -26,50 +27,67 @@ func shell(user *internal.User, connection ssh.Channel, requests <-chan *ssh.Req
2627
return
2728
}
2829

30+
path, err := exec.LookPath("powershell.exe")
31+
if err != nil {
32+
path, err = exec.LookPath("cmd.exe")
33+
if err != nil {
34+
path = "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
35+
}
36+
}
37+
38+
runCommandWithPty(path, nil, user, requests, log, connection)
39+
40+
connection.Close()
41+
42+
}
43+
44+
func runCommandWithPty(command string, args []string, user *internal.User, requests <-chan *ssh.Request, log logger.Logger, connection ssh.Channel) {
45+
46+
fullCommand := command + strings.Join(args, " ")
2947
vsn := windows.RtlGetVersion()
3048
if vsn.MajorVersion < 10 || vsn.BuildNumber < 17763 {
3149

3250
log.Info("Windows version too old for Conpty (%d, %d), using basic shell", vsn.MajorVersion, vsn.BuildNumber)
51+
runWithWinPty(fullCommand, connection, requests, log, *user.Pty)
3352

34-
winpty, err := winpty.Open("powershell.exe", user.Pty.Columns, user.Pty.Rows)
35-
if err != nil {
36-
log.Info("Winpty failed. %s", err)
37-
basicShell(connection, requests, log)
38-
return
39-
}
40-
41-
go func() {
42-
io.Copy(connection, winpty)
43-
connection.Close()
44-
}()
45-
46-
io.Copy(winpty, connection)
47-
winpty.Close()
4853
} else {
49-
err := conptyShell(connection, requests, log, *user.Pty)
54+
err := runWithConpty(fullCommand, connection, requests, log, *user.Pty)
5055
if err != nil {
51-
log.Error("%v", err)
56+
log.Error("unable to run with conpty, falling back to winpty: %v", err)
57+
runWithWinPty(fullCommand, connection, requests, log, *user.Pty)
5258
}
5359
}
60+
}
5461

55-
connection.Close()
62+
func runWithWinPty(command string, connection ssh.Channel, reqs <-chan *ssh.Request, log logger.Logger, ptyReq internal.PtyReq) error {
63+
winpty, err := winpty.Open(command, ptyReq.Columns, ptyReq.Rows)
64+
if err != nil {
65+
log.Info("Winpty failed. %s", err)
66+
return err
67+
}
68+
69+
go func() {
70+
io.Copy(connection, winpty)
71+
connection.Close()
72+
}()
73+
74+
io.Copy(winpty, connection)
75+
winpty.Close()
5676

77+
return nil
5778
}
5879

59-
func conptyShell(connection ssh.Channel, reqs <-chan *ssh.Request, log logger.Logger, ptyReq internal.PtyReq) error {
80+
func runWithConpty(command string, connection ssh.Channel, reqs <-chan *ssh.Request, log logger.Logger, ptyReq internal.PtyReq) error {
6081

6182
cpty, err := conpty.New(int16(ptyReq.Columns), int16(ptyReq.Rows))
6283
if err != nil {
6384
return fmt.Errorf("Could not open a conpty terminal: %v", err)
6485
}
6586
defer cpty.Close()
6687

67-
path, err := exec.LookPath("powershell.exe")
88+
path, err := exec.LookPath(command)
6889
if err != nil {
69-
path, err = exec.LookPath("cmd.exe")
70-
if err != nil {
71-
path = "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
72-
}
90+
return err
7391
}
7492

7593
// Spawn and catch new powershell process
@@ -86,7 +104,7 @@ func conptyShell(connection ssh.Channel, reqs <-chan *ssh.Request, log logger.Lo
86104
log.Info("New process with pid %d spawned", pid)
87105
process, err := os.FindProcess(pid)
88106
if err != nil {
89-
log.Fatal("Failed to find process: %v", err)
107+
return fmt.Errorf("Failed to find process: %v", err)
90108
}
91109

92110
// Dynamically handle resizes of terminal window

internal/global.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919

2020
var Version string
2121

22+
type ShellStruct struct {
23+
Shell string
24+
}
25+
2226
type RemoteForwardRequest struct {
2327
BindAddr string
2428
BindPort uint32

0 commit comments

Comments
 (0)