@@ -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
@@ -77,46 +79,112 @@ func (serveShell *Server) CreateFlags() {
77
79
}
78
80
79
81
// 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 {
82
84
// Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually
83
85
// configured.
84
86
var shutdown atomic.Bool
85
87
shutdown .Store (false )
86
- channel .Shutdown = & shutdown
88
+ ch .Shutdown = & shutdown
87
89
}
88
- serveShell .channel = channel
90
+ serveShell .pastTimeout .Store (false )
91
+ serveShell .channel = ch
89
92
if len (serveShell .HTTPAddr ) == 0 {
90
93
output .PrintFrameworkError ("User must specify -httpServeFile.BindAddr" )
91
94
92
95
return false
93
96
}
94
- channel .HTTPAddr = serveShell .HTTPAddr
95
- channel .HTTPPort = serveShell .HTTPPort
97
+ ch .HTTPAddr = serveShell .HTTPAddr
98
+ ch .HTTPPort = serveShell .HTTPPort
96
99
97
- if ! httpservefile .GetInstance ().Init (channel ) {
100
+ if ! httpservefile .GetInstance ().Init (ch ) {
98
101
return false
99
102
}
100
103
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
+ }
101
113
if serveShell .SSLShell {
102
- return sslshell .GetInstance ().Init (channel )
114
+ return sslshell .GetInstance ().Init (shellChannel )
103
115
}
104
116
105
- return simpleshell .GetServerInstance ().Init (channel )
117
+ return simpleshell .GetServerInstance ().Init (shellChannel )
106
118
}
107
119
108
120
// Shutdown triggers the shutdown for all running C2s.
109
121
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.
111
133
//
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 )
114
141
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
+ }
116
164
} 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
+ }
118
187
}
119
- httpservefile .GetInstance ().Channel ().Shutdown .Store (true )
120
188
121
189
return true
122
190
}
@@ -141,6 +209,10 @@ func (serveShell *Server) Run(timeout int) {
141
209
}
142
210
}
143
211
}()
212
+ go func () {
213
+ time .Sleep (time .Duration (timeout ) * time .Second )
214
+ serveShell .pastTimeout .Store (true )
215
+ }()
144
216
// Spin up the shell
145
217
wg .Add (1 )
146
218
go func () {
@@ -150,7 +222,7 @@ func (serveShell *Server) Run(timeout int) {
150
222
go func () {
151
223
for {
152
224
if sslshell .GetInstance ().Channel ().Shutdown .Load () {
153
- sslshell . GetInstance ().Shutdown ( )
225
+ serveShell . Channel ().Shutdown . Store ( true )
154
226
wg .Done ()
155
227
156
228
break
@@ -163,7 +235,7 @@ func (serveShell *Server) Run(timeout int) {
163
235
go func () {
164
236
for {
165
237
if simpleshell .GetServerInstance ().Channel ().Shutdown .Load () {
166
- simpleshell . GetServerInstance ().Shutdown ( )
238
+ serveShell . Channel ().Shutdown . Store ( true )
167
239
wg .Done ()
168
240
169
241
break
@@ -175,9 +247,7 @@ func (serveShell *Server) Run(timeout int) {
175
247
176
248
// Spin up the http server
177
249
wg .Add (1 )
178
- go func () {
179
- httpservefile .GetInstance ().Run (timeout )
180
- }()
250
+ go func () { httpservefile .GetInstance ().Run (timeout ) }()
181
251
182
252
// wait until the go routines are clean up
183
253
wg .Wait ()
0 commit comments