Skip to content

Commit d7337e1

Browse files
Added the fix
1 parent b49ac95 commit d7337e1

File tree

3 files changed

+111
-30
lines changed

3 files changed

+111
-30
lines changed

c2/cli/basic.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import (
77
"strings"
88
"sync"
99
"time"
10+
"sync/atomic"
1011

1112
"github.com/vulncheck-oss/go-exploit/output"
1213
"github.com/vulncheck-oss/go-exploit/protocol"
1314
)
1415

1516
// A very basic reverse/bind shell handler.
1617
func Basic(conn net.Conn) {
18+
var shutdown int32 = 0
1719
// Create channels for communication between goroutines.
1820
responseCh := make(chan string)
19-
quit := make(chan struct{})
2021

2122
// Use a WaitGroup to wait for goroutines to finish.
2223
var wg sync.WaitGroup
@@ -25,11 +26,17 @@ func Basic(conn net.Conn) {
2526
wg.Add(1)
2627
go func() {
2728
defer wg.Done()
29+
defer func (){
30+
// Signals for both routines to stop, this should get triggered when socket is closed
31+
// and causes it to fail the read
32+
atomic.StoreInt32(&shutdown, 1)
33+
}()
2834
responseBuffer := make([]byte, 1024)
2935
for {
30-
select {
31-
case <-quit:
36+
if atomic.LoadInt32(&shutdown) == 1 {
3237
return
38+
}
39+
select {
3340
default:
3441
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
3542
bytesRead, err := conn.Read(responseBuffer)
@@ -53,32 +60,38 @@ func Basic(conn net.Conn) {
5360
wg.Add(1)
5461
go func() {
5562
defer wg.Done()
56-
for response := range responseCh {
57-
select {
58-
case <-quit:
63+
for {
64+
if atomic.LoadInt32(&shutdown) == 1 {
5965
return
60-
default:
66+
}
67+
select {
68+
case response := <-responseCh:
6169
output.PrintShell(response)
70+
default:
6271
}
6372
}
6473
}()
6574

66-
for {
67-
// read user input until they type 'exit\n' or the socket breaks
68-
// note that ReadString is blocking, so they won't know the socket
69-
// is broken until they attempt to write something
70-
reader := bufio.NewReader(os.Stdin)
71-
command, _ := reader.ReadString('\n')
72-
ok := protocol.TCPWrite(conn, []byte(command))
73-
if !ok || command == "exit\n" {
74-
break
75+
go func(){
76+
// no waitgroup for this one because blocking IO, but this should not matter
77+
// since we are intentionally not trying to be a multi-implant C2 framework.
78+
// There still remains the issue that you would need to hit enter to find out
79+
// that the socket is dead but at least we can stop Basic() regardless of this fact.
80+
// This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842
81+
for {
82+
reader := bufio.NewReader(os.Stdin)
83+
command,_ := reader.ReadString('\n')
84+
if atomic.LoadInt32(&shutdown) == 1 {
85+
break
86+
}
87+
ok := protocol.TCPWrite(conn, []byte(command))
88+
if !ok || command == "exit\n" {
89+
break
90+
}
7591
}
76-
}
77-
78-
// signal for everyone to shutdown
79-
quit <- struct{}{}
80-
close(responseCh)
92+
}()
8193

8294
// wait until the go routines are clean up
8395
wg.Wait()
96+
close(responseCh)
8497
}

c2/simpleshell/simpleshellserver.go

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ import (
1818
// The server can accept multiple connections, but the user has no way of swapping between them unless
1919
// the terminate the connection.
2020
type Server struct {
21+
// The socket the server is listening on
2122
Listener net.Listener
23+
// Allows for us to track this from payloads, tells us if we have a shell
24+
Success bool
25+
// Lets us know if the server has completed Run(), you can combine this with
26+
// Success to signal cleanup operations
27+
Finished bool
2228
}
2329

2430
var serverSingleton *Server
31+
var clientChan = make(chan net.Conn, 100)
32+
var wg sync.WaitGroup
2533

2634
// A basic singleton interface for the c2.
2735
func GetServerInstance() *Server {
@@ -57,18 +65,41 @@ func (shellServer *Server) Init(channel channel.Channel) bool {
5765
return true
5866
}
5967

68+
func (shellServer *Server) KillServer() bool {
69+
output.PrintFrameworkStatus("Received shutdown, killing server and client sockets")
70+
shellServer.Listener.Close()
71+
for {
72+
select {
73+
case conn := <-clientChan:
74+
conn.Close()
75+
output.PrintfFrameworkStatus("Force closed socket for: %s", conn.RemoteAddr())
76+
default:
77+
if len(clientChan) == 0 {
78+
output.PrintFrameworkDebug("No more clients to kill:")
79+
80+
return true
81+
}
82+
}
83+
}
84+
}
85+
86+
6087
// Listen for incoming.
6188
func (shellServer *Server) Run(timeout int) {
89+
defer func (){
90+
shellServer.Finished = true
91+
}()
92+
6293
// mutex for user input
6394
var cliLock sync.Mutex
6495

6596
// track if we got a shell or not
66-
success := false
97+
shellServer.Success = false
6798

6899
// terminate the server if no shells come in within timeout seconds
69100
go func() {
70101
time.Sleep(time.Duration(timeout) * time.Second)
71-
if !success {
102+
if !shellServer.Success {
72103
output.PrintFrameworkError("Timeout met. Shutting down shell listener.")
73104
shellServer.Listener.Close()
74105
}
@@ -83,15 +114,18 @@ func (shellServer *Server) Run(timeout int) {
83114
output.PrintFrameworkError(err.Error())
84115
}
85116

86-
return
117+
break
87118
}
88-
success = true
89-
output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr())
119+
shellServer.Success = true
120+
wg.Add(1)
121+
clientChan <- client
90122
go handleSimpleConn(client, &cliLock, client.RemoteAddr())
91123
}
124+
wg.Wait()
92125
}
93126

94127
func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr) {
128+
defer wg.Done()
95129
// connections will stack up here. Currently that will mean a race
96130
// to the next connection but we can add in attacker handling of
97131
// connections latter

c2/sslshell/sslshellserver.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ type Server struct {
3838
PrivateKeyFile string
3939
// The file path to the user provided certificate (if provided)
4040
CertificateFile string
41+
// Allows for us to track this from payloads, tells us if we have a shell
42+
Success bool
43+
// Lets us know if the server has completed Run(), you can combine this with
44+
// Success to signal cleanup operations
45+
Finished bool
4146
}
4247

48+
49+
var clientChan = make(chan net.Conn, 100)
50+
var wg sync.WaitGroup
4351
var singleton *Server
4452

4553
// Get a singleton instance of the sslserver c2.
@@ -61,6 +69,24 @@ func (shellServer *Server) CreateFlags() {
6169
}
6270
}
6371

72+
func (shellServer *Server) KillServer() bool {
73+
output.PrintFrameworkStatus("Received shutdown, killing server and client sockets")
74+
shellServer.Listener.Close()
75+
for {
76+
select {
77+
case conn := <-clientChan:
78+
conn.Close()
79+
output.PrintfFrameworkStatus("Force closed socket for: %s", conn.RemoteAddr())
80+
default:
81+
if len(clientChan) == 0 {
82+
output.PrintFrameworkDebug("No more clients to kill:")
83+
84+
return true
85+
}
86+
}
87+
}
88+
}
89+
6490
// Parses the user provided files or generates the certificate files and starts
6591
// the TLS listener on the user provided IP/port.
6692
func (shellServer *Server) Init(channel channel.Channel) bool {
@@ -106,16 +132,20 @@ func (shellServer *Server) Init(channel channel.Channel) bool {
106132

107133
// Listens for incoming SSL/TLS connections spawns a reverse shell handler for each new connection.
108134
func (shellServer *Server) Run(timeout int) {
135+
shellServer.Finished = false
136+
defer func (){
137+
shellServer.Finished = true
138+
}()
109139
// mutex for user input
110140
var cliLock sync.Mutex
111141

112142
// track if we got a shell or not
113-
success := false
143+
shellServer.Success = false
114144

115145
// terminate the server if no shells come in within timeout seconds
116146
go func() {
117147
time.Sleep(time.Duration(timeout) * time.Second)
118-
if !success {
148+
if !shellServer.Success {
119149
output.PrintFrameworkError("Timeout met. Shutting down shell listener.")
120150
shellServer.Listener.Close()
121151
}
@@ -130,15 +160,19 @@ func (shellServer *Server) Run(timeout int) {
130160
output.PrintFrameworkError(err.Error())
131161
}
132162

133-
return
163+
break
134164
}
135-
success = true
165+
shellServer.Success = true
136166
output.PrintfFrameworkSuccess("Caught new shell from %v", client.RemoteAddr())
167+
clientChan <- client
168+
wg.Add(1)
137169
go handleSimpleConn(client, &cliLock, client.RemoteAddr())
138170
}
171+
wg.Wait()
139172
}
140173

141174
func handleSimpleConn(conn net.Conn, cliLock *sync.Mutex, remoteAddr net.Addr) {
175+
defer wg.Done()
142176
// connections will stack up here. Currently that will mean a race
143177
// to the next connection but we can add in attacker handling of
144178
// connections latter

0 commit comments

Comments
 (0)