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  }