@@ -34,6 +34,7 @@ import (
34
34
"flag"
35
35
"sync"
36
36
"sync/atomic"
37
+ "time"
37
38
38
39
"github.com/vulncheck-oss/go-exploit/c2/channel"
39
40
"github.com/vulncheck-oss/go-exploit/c2/httpservefile"
@@ -50,7 +51,8 @@ type Server struct {
50
51
// The HTTP port to bind to
51
52
HTTPPort int
52
53
// The underlying C2 channel with metadata and session information
53
- channel * channel.Channel
54
+ channel * channel.Channel
55
+ pastTimeout atomic.Bool
54
56
}
55
57
56
58
var singleton * Server
@@ -85,6 +87,7 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
85
87
shutdown .Store (false )
86
88
ch .Shutdown = & shutdown
87
89
}
90
+ serveShell .pastTimeout .Store (false )
88
91
serveShell .channel = ch
89
92
if len (serveShell .HTTPAddr ) == 0 {
90
93
output .PrintFrameworkError ("User must specify -httpServeFile.BindAddr" )
@@ -107,7 +110,6 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
107
110
IsClient : false ,
108
111
Shutdown : & shutdown ,
109
112
}
110
- shellChannel .Shutdown = & shutdown
111
113
if serveShell .SSLShell {
112
114
return sslshell .GetInstance ().Init (shellChannel )
113
115
}
@@ -117,20 +119,72 @@ func (serveShell *Server) Init(ch *channel.Channel) bool {
117
119
118
120
// Shutdown triggers the shutdown for all running C2s.
119
121
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:
121
126
//
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 )
124
141
if serveShell .SSLShell {
125
142
if ! sslshell .GetInstance ().Channel ().HasSessions () {
126
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
+ }
127
163
}
128
164
} else {
129
165
if ! simpleshell .GetServerInstance ().Channel ().HasSessions () {
130
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
+ }
131
186
}
132
187
}
133
- httpservefile .GetInstance ().Channel ().Shutdown .Store (true )
134
188
135
189
return true
136
190
}
@@ -155,6 +209,10 @@ func (serveShell *Server) Run(timeout int) {
155
209
}
156
210
}
157
211
}()
212
+ go func () {
213
+ time .Sleep (time .Duration (timeout ) * time .Second )
214
+ serveShell .pastTimeout .Store (true )
215
+ }()
158
216
// Spin up the shell
159
217
wg .Add (1 )
160
218
go func () {
@@ -189,16 +247,7 @@ func (serveShell *Server) Run(timeout int) {
189
247
190
248
// Spin up the http server
191
249
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 ) }()
202
251
203
252
// wait until the go routines are clean up
204
253
wg .Wait ()
0 commit comments