Skip to content

Commit 0d1c267

Browse files
committed
Create ServeShell logic for the c2 lacking sessions.
1 parent 1c1979d commit 0d1c267

File tree

1 file changed

+65
-16
lines changed

1 file changed

+65
-16
lines changed

c2/httpserveshell/httpserveshell.go

Lines changed: 65 additions & 16 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
@@ -85,6 +87,7 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
8587
shutdown.Store(false)
8688
ch.Shutdown = &shutdown
8789
}
90+
serveShell.pastTimeout.Store(false)
8891
serveShell.channel = ch
8992
if len(serveShell.HTTPAddr) == 0 {
9093
output.PrintFrameworkError("User must specify -httpServeFile.BindAddr")
@@ -107,7 +110,6 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
107110
IsClient: false,
108111
Shutdown: &shutdown,
109112
}
110-
shellChannel.Shutdown = &shutdown
111113
if serveShell.SSLShell {
112114
return sslshell.GetInstance().Init(shellChannel)
113115
}
@@ -117,20 +119,72 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
117119

118120
// Shutdown triggers the shutdown for all running C2s.
119121
func (serveShell *Server) Shutdown() bool {
120-
// 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:
121126
//
122-
// Each of the shell sessions and httpservefile sessions should be handled by the channel Shutdown
123-
// atomic.
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.
133+
//
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)
124141
if serveShell.SSLShell {
125142
if !sslshell.GetInstance().Channel().HasSessions() {
126143
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+
}
127163
}
128164
} else {
129165
if !simpleshell.GetServerInstance().Channel().HasSessions() {
130166
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+
}
131186
}
132187
}
133-
httpservefile.GetInstance().Channel().Shutdown.Store(true)
134188

135189
return true
136190
}
@@ -155,6 +209,10 @@ func (serveShell *Server) Run(timeout int) {
155209
}
156210
}
157211
}()
212+
go func() {
213+
time.Sleep(time.Duration(timeout) * time.Second)
214+
serveShell.pastTimeout.Store(true)
215+
}()
158216
// Spin up the shell
159217
wg.Add(1)
160218
go func() {
@@ -189,16 +247,7 @@ func (serveShell *Server) Run(timeout int) {
189247

190248
// Spin up the http server
191249
wg.Add(1)
192-
go func() {
193-
httpservefile.GetInstance().Run(timeout)
194-
for {
195-
if serveShell.Channel().Shutdown.Load() {
196-
serveShell.Shutdown()
197-
198-
break
199-
}
200-
}
201-
}()
250+
go func() { httpservefile.GetInstance().Run(timeout) }()
202251

203252
// wait until the go routines are clean up
204253
wg.Wait()

0 commit comments

Comments
 (0)