github.com/iDigitalFlame/xmt@v0.5.4/c2/cfg/group.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 cfg 18 19 import ( 20 "context" 21 "net" 22 "sync" 23 "time" 24 25 "github.com/iDigitalFlame/xmt/data" 26 "github.com/iDigitalFlame/xmt/util" 27 "github.com/iDigitalFlame/xmt/util/xerr" 28 ) 29 30 const ( 31 // SelectorLastValid is the default selection that will keep using the last 32 // Group unless it fails. On a failure (or the first call), this will act 33 // similar to 'SelectorRoundRobin'. 34 // 35 // Takes effect only if there are multiple Groups in this Config. 36 // This value is GLOBAL and can be present in any Group! 37 SelectorLastValid = cBit(0xAA) 38 // SelectorRoundRobin is a selection option that will simply select the NEXT 39 // Group on every connection attempt. This option is affected by the Group 40 // weights set on each addition and will prefer higher numbered options in 41 // order. 42 // 43 // Takes effect only if there are multiple Groups in this Config. 44 // This value is GLOBAL and can be present in any Group! 45 SelectorRoundRobin = cBit(0xAB) 46 // SelectorRandom is a selection option that will ignore all weights and order 47 // and will select an entry from the list randomly. 48 // 49 // Takes effect only if there are multiple Groups in this Config. 50 // This value is GLOBAL and can be present in any Group! 51 SelectorRandom = cBit(0xAC) 52 // SelectorSemiRoundRobin is a selection option that will potentially select 53 // the NEXT Group dependent on a random (25%) chance on every connection 54 // attempt. This option is affected by the Group weights set on each addition 55 // and will prefer higher numbered options in order. Otherwise, the last 56 // group used is kept. 57 // 58 // Takes effect only if there are multiple Groups in this Config. 59 // This value is GLOBAL and can be present in any Group! 60 SelectorSemiRoundRobin = cBit(0xAD) 61 // SelectorSemiRandom is a selection option that will ignore all weights and 62 // order and will select an entry from the list randomly dependent on a 63 // random (25%) chance on every connection attempt. Otherwise, the last 64 // group used is kept. 65 // 66 // Takes effect only if there are multiple Groups in this Config. 67 // This value is GLOBAL and can be present in any Group! 68 SelectorSemiRandom = cBit(0xAE) 69 ) 70 71 // Group is a struct that allows for using multiple connections for a single 72 // Session. 73 // 74 // Groups are automatically created when a Config is built into a Profile 75 // that contains multiple Profile 'Groups'. 76 type Group struct { 77 cur *profile 78 entries []*profile 79 src []byte 80 lock sync.Mutex 81 sel uint8 82 } 83 type profile struct { 84 kill time.Time 85 w Wrapper 86 t Transform 87 conn interface{} 88 work *WorkHours 89 keys []uint32 90 src []byte 91 hosts []string 92 sleep time.Duration 93 kds bool 94 weight uint8 95 jitter int8 96 } 97 98 func (g *Group) init() { 99 if g.cur == nil { 100 g.Switch(false) 101 } 102 } 103 104 // Len implements the 'sort.Interface' interface, this allows for a Group to 105 // be sorted. 106 func (g *Group) Len() int { 107 return len(g.entries) 108 } 109 110 // Jitter returns a value that represents a percentage [0-100] that will be taken 111 // into account by a Session in order to skew its connection timeframe. 112 // 113 // The value zero (0) is used to signify that Jitter is disabled. Other values 114 // greater than one hundred (100) are ignored, as well as values below zero. 115 // 116 // The special value '-1' indicates that this Profile does not set a Jitter value 117 // and to use the system default '10%'. 118 func (g *Group) Jitter() int8 { 119 if g.init(); g.cur == nil { 120 return -1 121 } 122 return g.cur.jitter 123 } 124 125 // Swap implements the 'sort.Interface' interface, this allows for a Group to be 126 // sorted. 127 func (g *Group) Swap(i, j int) { 128 g.entries[i], g.entries[j] = g.entries[j], g.entries[i] 129 } 130 func (p *profile) Jitter() int8 { 131 return p.jitter 132 } 133 func (profile) Switch(_ bool) bool { 134 return false 135 } 136 137 // Less implements the 'sort.Interface' interface, this allows for a Group to be 138 // sorted. 139 func (g *Group) Less(i, j int) bool { 140 return g.entries[i].weight > g.entries[j].weight 141 } 142 143 // Switch is function that will indicate to the caller if the 'Next' function 144 // needs to be called. Calling this function has the potential to advance the 145 // Profile group, if available. 146 // 147 // The supplied boolean must be true if the last call to 'Connect' ot 'Listen' 148 // resulted in an error or if a forced switch if warranted. 149 // This indicates to the Profile is "dirty" and a switchover must be done. 150 // 151 // It is recommended to call the 'Next' function after if the result of this 152 // function is true. 153 // 154 // Static Profile variants may always return 'false' to prevent allocations. 155 func (g *Group) Switch(e bool) bool { 156 if (g.cur != nil && !e && g.sel == 0) || len(g.entries) == 0 { 157 return false 158 } 159 if g.sel == 0 && !e && g.cur != nil { 160 return false 161 } 162 if g.cur != nil && (g.sel == 3 || g.sel == 4) && util.FastRandN(4) != 0 { 163 return false 164 } 165 if g.lock.Lock(); g.sel == 2 || g.sel == 4 { 166 if n := g.entries[util.FastRandN(len(g.entries))]; g.cur != n { 167 g.cur = n 168 g.lock.Unlock() 169 return true 170 } 171 g.lock.Unlock() 172 return false 173 } 174 if g.cur == nil { 175 g.cur = g.entries[0] 176 g.lock.Unlock() 177 return true 178 } 179 var f bool 180 for i := range g.entries { 181 if g.entries[i] == g.cur { 182 f = true 183 continue 184 } 185 if f { 186 g.cur = g.entries[i] 187 g.lock.Unlock() 188 return true 189 } 190 } 191 if f && g.cur == g.entries[0] { 192 g.lock.Unlock() 193 return false 194 } 195 g.cur = g.entries[0] 196 g.lock.Unlock() 197 return true 198 } 199 200 // Sleep returns a value that indicates the amount of time a Session should wait 201 // before attempting communication again, modified by Jitter (if enabled). 202 // 203 // Sleep MUST be greater than zero (0), any value that is zero or less is ignored 204 // and indicates that this profile does not set a Sleep value and will use the 205 // system default '60s'. 206 func (g *Group) Sleep() time.Duration { 207 if g.init(); g.cur == nil { 208 return -1 209 } 210 return g.cur.sleep 211 } 212 213 // WorkHours returns a value that indicates when the Session should be active 214 // and communicate with the C2 Server. The returned struct can be used to 215 // determine which days the Session can connect. 216 // 217 // If the returned value is nil, there are no Working hours restrictions. 218 func (g *Group) WorkHours() *WorkHours { 219 if g.init(); g.cur == nil { 220 return nil 221 } 222 return g.cur.work 223 } 224 func (p *profile) Sleep() time.Duration { 225 return p.sleep 226 } 227 func (p *profile) WorkHours() *WorkHours { 228 return p.work 229 } 230 231 // KillDate returns a value that indicates the date and time when the Session will 232 // stop functioning. 233 // 234 // If the supplied boolean is false, there is no 'KillDate' set. 235 func (g *Group) KillDate() (time.Time, bool) { 236 if g.init(); g.cur == nil { 237 return time.Time{}, false 238 } 239 return g.cur.kill, g.cur.kds 240 } 241 func (p *profile) KillDate() (time.Time, bool) { 242 return p.kill, p.kds 243 } 244 245 // MarshalBinary allows the source of this Group to be retrieved to be reused 246 // again. 247 // 248 // This function returns an error if the source is not available. 249 func (g *Group) MarshalBinary() ([]byte, error) { 250 if len(g.src) > 0 { 251 return g.src, nil 252 } 253 return nil, xerr.Sub("binary source not available", 0x60) 254 } 255 func (p *profile) MarshalBinary() ([]byte, error) { 256 if len(p.src) > 0 { 257 return p.src, nil 258 } 259 return nil, xerr.Sub("binary source not available", 0x60) 260 } 261 262 // TrustedKey returns true if the supplied Server PublicKey is trusted. 263 // Empty PublicKeys will always return false. 264 // 265 // This function returns true if no trusted PublicKey hashes are configured or 266 // the hash was found. 267 func (g *Group) TrustedKey(k data.PublicKey) bool { 268 if g.init(); g.cur == nil { 269 return !k.Empty() 270 } 271 return g.cur.TrustedKey(k) 272 } 273 func (p *profile) TrustedKey(k data.PublicKey) bool { 274 if k.Empty() { 275 return false 276 } 277 if len(p.keys) == 0 { 278 return true 279 } 280 h := k.Hash() 281 for i := range p.keys { 282 if p.keys[i] == h { 283 return true 284 } 285 } 286 return false 287 } 288 289 // Next is a function call that can be used to grab the Profile's current target 290 // along with the appropriate Wrapper and Transform. 291 // 292 // Implementations of a Profile are recommend to ensure that this function does 293 // not affect how the Profile currently works until a call to 'Switch' as this 294 // WILL be called on startup of a Session. 295 func (g *Group) Next() (string, Wrapper, Transform) { 296 if g.init(); g.cur == nil { 297 return "", nil, nil 298 } 299 return g.cur.Next() 300 } 301 func (p *profile) Next() (string, Wrapper, Transform) { 302 if len(p.hosts) == 0 { 303 return "", p.w, p.t 304 } 305 if len(p.hosts) == 1 { 306 return p.hosts[0], p.w, p.t 307 } 308 return p.hosts[util.FastRandN(len(p.hosts))], p.w, p.t 309 } 310 311 // Connect is a function that will preform a Connection attempt against the 312 // supplied address string. This function may return an error if a connection 313 // could not be made or if this Profile does not support Client-side connections. 314 // 315 // It is recommended for implementations to implement using the passed Context 316 // to stop in-flight calls. 317 func (g *Group) Connect(x context.Context, s string) (net.Conn, error) { 318 if g.init(); g.cur == nil { 319 return nil, ErrNotAConnector 320 } 321 return g.cur.Connect(x, s) 322 } 323 func (p *profile) Connect(x context.Context, s string) (net.Conn, error) { 324 c, ok := p.conn.(Connector) 325 if !ok { 326 return nil, ErrNotAConnector 327 } 328 return c.Connect(x, s) 329 } 330 331 // Listen is a function that will attempt to create a listening connection on 332 // the supplied address string. This function may return an error if a listener 333 // could not be created or if this Profile does not support Server-side connections. 334 // 335 // It is recommended for implementations to implement using the passed Context 336 // to stop running Listeners. 337 func (g *Group) Listen(x context.Context, s string) (net.Listener, error) { 338 if g.init(); g.cur == nil { 339 return nil, ErrNotAListener 340 } 341 return g.cur.Listen(x, s) 342 } 343 func (p *profile) Listen(x context.Context, s string) (net.Listener, error) { 344 l, ok := p.conn.(Accepter) 345 if !ok { 346 return nil, ErrNotAListener 347 } 348 return l.Listen(x, s) 349 }