Skip to content

Commit 7c2817d

Browse files
authored
Merge pull request #367 from vulncheck-oss/fix/unify-broken-httpserveshell
Fix OS signal handling on HTTPServeShell with Active Sessions
2 parents c5a180b + 0d1c267 commit 7c2817d

File tree

2 files changed

+92
-22
lines changed

2 files changed

+92
-22
lines changed

c2/httpserveshell/httpserveshell.go

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"flag"
3535
"sync"
3636
"sync/atomic"
37+
"time"
3738

3839
"github.com/vulncheck-oss/go-exploit/c2/channel"
3940
"github.com/vulncheck-oss/go-exploit/c2/httpservefile"
@@ -50,7 +51,8 @@ type Server struct {
5051
// The HTTP port to bind to
5152
HTTPPort int
5253
// The underlying C2 channel with metadata and session information
53-
channel *channel.Channel
54+
channel *channel.Channel
55+
pastTimeout atomic.Bool
5456
}
5557

5658
var singleton *Server
@@ -77,46 +79,112 @@ func (serveShell *Server) CreateFlags() {
7779
}
7880

7981
// load the provided file into memory. Generate the random filename.
80-
func (serveShell *Server) Init(channel *channel.Channel) bool {
81-
if channel.Shutdown == nil {
82+
func (serveShell *Server) Init(ch *channel.Channel) bool {
83+
if ch.Shutdown == nil {
8284
// Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually
8385
// configured.
8486
var shutdown atomic.Bool
8587
shutdown.Store(false)
86-
channel.Shutdown = &shutdown
88+
ch.Shutdown = &shutdown
8789
}
88-
serveShell.channel = channel
90+
serveShell.pastTimeout.Store(false)
91+
serveShell.channel = ch
8992
if len(serveShell.HTTPAddr) == 0 {
9093
output.PrintFrameworkError("User must specify -httpServeFile.BindAddr")
9194

9295
return false
9396
}
94-
channel.HTTPAddr = serveShell.HTTPAddr
95-
channel.HTTPPort = serveShell.HTTPPort
97+
ch.HTTPAddr = serveShell.HTTPAddr
98+
ch.HTTPPort = serveShell.HTTPPort
9699

97-
if !httpservefile.GetInstance().Init(channel) {
100+
if !httpservefile.GetInstance().Init(ch) {
98101
return false
99102
}
100103

104+
// Initialize the shell server channels with variables from upstream
105+
var shutdown atomic.Bool
106+
shutdown.Store(false)
107+
shellChannel := &channel.Channel{
108+
IPAddr: ch.IPAddr,
109+
Port: ch.Port,
110+
IsClient: false,
111+
Shutdown: &shutdown,
112+
}
101113
if serveShell.SSLShell {
102-
return sslshell.GetInstance().Init(channel)
114+
return sslshell.GetInstance().Init(shellChannel)
103115
}
104116

105-
return simpleshell.GetServerInstance().Init(channel)
117+
return simpleshell.GetServerInstance().Init(shellChannel)
106118
}
107119

108120
// Shutdown triggers the shutdown for all running C2s.
109121
func (serveShell *Server) Shutdown() bool {
110-
// Since no logic actually handles client interactions here, this is stub.
122+
// This is a bit confusing at first glance, but it solves the fact that this c2 doesn't directly
123+
// keep track of sessions and we can't differentiate between a timeout "done" and a signal "done".
124+
// What this means is that if a underlying shell server has sessions that we want to keep open for
125+
// use after callbacks occur we have to account for a few things:
126+
//
127+
// - If serveShell shutdown is called and there are no sessions, just trigger shutdown on the
128+
// underlying shell server (easy).
129+
// - If the server does have sessions, we have a few issues. The serveShell shutdown is triggered
130+
// from a shutdown call, meaning that it's already in a closing state. There is no way to tell
131+
// if it was an OS signal or a timeout anymore and now if a background shell is running and we
132+
// are closing serveShell we cannot catch the signal and pass the shutdown to the shell server.
111133
//
112-
// Each of the shell sessions and httpservefile sessions should be handled by the channel Shutdown
113-
// atomic.
134+
// In order to solve the second, we added a `pastTimeout` atomic that only signals if we are past
135+
// timeout. Now, when timeout is reached and there's a background shell (the positive case) we
136+
// reset the Shutdown atomic to false and then begin looping to check if it closes again, making
137+
// the server in a state that it knows it's past timeout and reactivating the server until a
138+
// signal is hit or the underlying server also shuts down.
139+
140+
httpservefile.GetInstance().Channel().Shutdown.Store(true)
114141
if serveShell.SSLShell {
115-
sslshell.GetInstance().Channel().Shutdown.Store(true)
142+
if !sslshell.GetInstance().Channel().HasSessions() {
143+
sslshell.GetInstance().Channel().Shutdown.Store(true)
144+
} else {
145+
// Session exist, reset the shutdown atomic and loop until a second shutdown occurs.
146+
serveShell.Channel().Shutdown.Store(false)
147+
for {
148+
if serveShell.Channel().Shutdown.Load() {
149+
// The the shutdown happens and it is past timeout, that means that
150+
// we have sessions but timeout has passed, so reset all atomics.
151+
// Now when the loop happens we will be able to check for other
152+
// shutdown signals of any kind.
153+
if serveShell.pastTimeout.Load() {
154+
serveShell.Channel().Shutdown.Store(false)
155+
serveShell.pastTimeout.Store(false)
156+
} else {
157+
sslshell.GetInstance().Channel().Shutdown.Store(true)
158+
159+
break
160+
}
161+
}
162+
}
163+
}
116164
} else {
117-
simpleshell.GetServerInstance().Channel().Shutdown.Store(true)
165+
if !simpleshell.GetServerInstance().Channel().HasSessions() {
166+
simpleshell.GetServerInstance().Channel().Shutdown.Store(true)
167+
} else {
168+
// Session exist, reset the shutdown atomic and loop until a second shutdown occurs.
169+
serveShell.Channel().Shutdown.Store(false)
170+
for {
171+
if serveShell.Channel().Shutdown.Load() {
172+
// The the shutdown happens and it is past timeout, that means that
173+
// we have sessions but timeout has passed, so reset all atomics.
174+
// Now when the loop happens we will be able to check for other
175+
// shutdown signals of any kind.
176+
if serveShell.pastTimeout.Load() {
177+
serveShell.Channel().Shutdown.Store(false)
178+
serveShell.pastTimeout.Store(false)
179+
} else {
180+
simpleshell.GetServerInstance().Channel().Shutdown.Store(true)
181+
182+
break
183+
}
184+
}
185+
}
186+
}
118187
}
119-
httpservefile.GetInstance().Channel().Shutdown.Store(true)
120188

121189
return true
122190
}
@@ -141,6 +209,10 @@ func (serveShell *Server) Run(timeout int) {
141209
}
142210
}
143211
}()
212+
go func() {
213+
time.Sleep(time.Duration(timeout) * time.Second)
214+
serveShell.pastTimeout.Store(true)
215+
}()
144216
// Spin up the shell
145217
wg.Add(1)
146218
go func() {
@@ -150,7 +222,7 @@ func (serveShell *Server) Run(timeout int) {
150222
go func() {
151223
for {
152224
if sslshell.GetInstance().Channel().Shutdown.Load() {
153-
sslshell.GetInstance().Shutdown()
225+
serveShell.Channel().Shutdown.Store(true)
154226
wg.Done()
155227

156228
break
@@ -163,7 +235,7 @@ func (serveShell *Server) Run(timeout int) {
163235
go func() {
164236
for {
165237
if simpleshell.GetServerInstance().Channel().Shutdown.Load() {
166-
simpleshell.GetServerInstance().Shutdown()
238+
serveShell.Channel().Shutdown.Store(true)
167239
wg.Done()
168240

169241
break
@@ -175,9 +247,7 @@ func (serveShell *Server) Run(timeout int) {
175247

176248
// Spin up the http server
177249
wg.Add(1)
178-
go func() {
179-
httpservefile.GetInstance().Run(timeout)
180-
}()
250+
go func() { httpservefile.GetInstance().Run(timeout) }()
181251

182252
// wait until the go routines are clean up
183253
wg.Wait()

c2/simpleshell/simpleshellserver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (shellServer *Server) Shutdown() bool {
4848
output.PrintFrameworkStatus("C2 received shutdown, killing server and client sockets for shell server")
4949
if len(shellServer.Channel().Sessions) > 0 {
5050
for k, session := range shellServer.Channel().Sessions {
51-
output.PrintfFrameworkStatus("Connection closed: %s", session.RemoteAddr)
51+
output.PrintfFrameworkStatus("Connection closed for shell server: %s", session.RemoteAddr)
5252
shellServer.Channel().RemoveSession(k)
5353
}
5454
}

0 commit comments

Comments
 (0)