- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 546
- Reaction score
- 7
Manual patching of shellcode byte arrays is a waste of time. If you're tired of defining hardcoded arrays and then calculating offsets to patch in addresses or constants at runtime, this C++ wrapper is what you need. It allows you to pass immediate values directly into the shellcode definition, handling the buffer management and type alignment for you.
Technical Overview
The class is designed to be lean and works in both Ring 3 and Ring 0. It uses variadic templates to process arguments during initialization. A crucial detail: it assumes int literals are intended to be single bytes. If you need to pass a 32-bit value or larger, you must use the ul suffix to prevent truncation.
Key Features:
Implementation
Usage Example
Suppose you need to call a specific address by moving it into RAX. Instead of calculating where that address starts in a
array, you just write this:
This is a solid base for anyone building custom injectors or manual mappers. If you implement a more robust type-safety check for the byte truncation, drop it in the thread.
Anyone tested this on the latest EAC build yet?
Technical Overview
The class is designed to be lean and works in both Ring 3 and Ring 0. It uses variadic templates to process arguments during initialization. A crucial detail: it assumes int literals are intended to be single bytes. If you need to pass a 32-bit value or larger, you must use the ul suffix to prevent truncation.
Key Features:
- Cross-compatibility — toggle between User and Kernel modes via
.Code:
_KERNEL_MODE - RAII Compliant — handles memory allocation, copying, and freeing automatically.
- Buffer Management — alignment-aware allocation based on page size (0x1000).
- Trivial Copy Checks — uses
to ensure types are safe for raw memory copying.Code:
static_assert
Implementation
Code:
class shellcode {
private:
unsigned char* buffer = nullptr;
SIZE_T allocation_size = 0;
SIZE_T buffer_size = 0;
static constexpr SIZE_T page_size = 0x1000;
static constexpr SIZE_T bytes_to_pages(const SIZE_T size) noexcept {
return (((size) >> 12) + (((size) & (0xFFF)) != 0));
}
void free_buf() noexcept {
if (buffer == nullptr) return;
#ifdef _KERNEL_MODE
ExFreePoolWithTag(buffer, 0);
#else
free(buffer);
#endif
buffer = nullptr;
allocation_size = 0;
buffer_size = 0;
}
void ensure_capacity(SIZE_T extra) {
if (buffer_size + extra > allocation_size) allocate_buf(buffer_size + extra);
}
void allocate_buf(SIZE_T size) {
if (size == 0) size = page_size;
const SIZE_T new_size = size <= page_size ? page_size : bytes_to_pages(size) * page_size;
if (buffer && allocation_size >= new_size) return;
free_buf();
allocation_size = new_size;
#ifdef _KERNEL_MODE
buffer = reinterpret_cast<unsigned char*>(ExAllocatePoolZero(NonPagedPoolNx, allocation_size, 0));
#else
buffer = reinterpret_cast<unsigned char*>(malloc(allocation_size));
memset(buffer, 0, allocation_size);
#endif
}
template<typename type>
void append_value(type value) {
static_assert(__is_trivially_copyable(type), "type must be trivially copyable");
ensure_capacity(sizeof(type));
memcpy(buffer + buffer_size, &value, sizeof(type));
buffer_size += sizeof(type);
}
void process_arg(int byte) {
ensure_capacity(1);
buffer[buffer_size++] = static_cast<UINT8>(byte);
}
template <typename type>
void process_arg(const type arg) {
append_value(arg);
}
public:
shellcode() = default;
template <typename... args_type>
shellcode(args_type... args) {
constexpr SIZE_T arg_size_bytes = (0 + ... + sizeof(args_type));
allocate_buf(arg_size_bytes);
(process_arg(args), ...);
}
~shellcode() noexcept { free_buf(); }
const unsigned char* get() const noexcept { return buffer; }
SIZE_T size() const noexcept { return buffer_size; }
void clear() noexcept { buffer_size = 0; }
};
Usage Example
Suppose you need to call a specific address by moving it into RAX. Instead of calculating where that address starts in a
Code:
uint8_t
Code:
const uintptr_t function_address = 0xDEADBEEFDEADBEEF;
shellcode test = {
0x48, 0xC7, 0xC1, 0x14ul, // mov rcx, 14h
0x48, 0xB8, function_address, // movabs rax, function_address
0xFF, 0xD0 // call rax
};
While the code includes
macros using
, the author hasn't stress-tested this inside a driver environment yet. Make sure your IRQL is appropriate for pool allocation (typically <= DISPATCH_LEVEL for NonPagedPool). If you're using this for APC injection or stealthy execution, ensure you nuke the buffer once it's copied into the target process memory.
Code:
_KERNEL_MODE
Code:
ExAllocatePoolZero
This is a solid base for anyone building custom injectors or manual mappers. If you implement a more robust type-safety check for the byte truncation, drop it in the thread.
Anyone tested this on the latest EAC build yet?