Building
Stubcore

Runtime-bound payload execution, generated stubs and why static loaders are boring.

Introduction

I have always had an active imagination, as well as an intense interest in the near sci-fi level offensive capability of a particular APT. You know the one.

Well, the chances that I would get hired there are quite low, for now. Most people would call it quits at that, "Damn, looks like I can't do cool shit :(". But not me. I got what the kids these days call free will. Combine that with that imagination I was talking about earlier and we can just play pretend with code like a kid playing GI Joe with a stick.

Thus stubcore is born, to enhance a workflow nearly nobody has. 4 fun.

The Problem With Static Loaders

Most loaders are the same thing wearing different trench coats.

Hardcoded key. XOR blob, maybe AES if you looked at a tutorial that week. Single injector. Same behavior every time it runs. Fine for a learning exercise, operationally D.O.A.

The real problem is not that static loaders are easy to detect, although they are. It is that if everything needed for decryption lives inside the stub, the payload is recoverable by anyone willing to spend fifteen minutes in x64dbg. The "encryption" is just a speed bump.

That reframes the question pretty quickly:

// Bad Question

"How do I hide the payload from EDR?"

// Better Question

"How do I rapidly produce and protect my payloads from nosey ass researchers and adversaries?"

That one question is basically the entire design document for stubcore.

The Architecture of a Loader Generator

The first thing to understand is that Stubcore isn’t actually a loader. It’s a loader factory.

That distinction is where the fun starts. Most static loaders are boring because they are predictable; you ship a binary, and it does the same dance every time. Stubcore, instead, runs a build pipeline that spits out purpose-built artifacts. It takes your injector source, compiles it into a compact PE, packages it with your payload, and then wraps the whole mess in an environment-aware stub.

If you build two stubs targeting different conditions, you get two fundamentally different files. It’s not just a generic loader cosplaying as flexible, it's a specific artifact for a specific moment.

text build pipeline
builder
 ├─ generate injector source
 ├─ compile injector
 ├─ package payload + injector
 ├─ encrypt composite
 └─ emit final stub

runtime
 ├─ derive runtime key
 ├─ decrypt composite
 ├─ parse injector
 ├─ execute injector
 └─ injector handles payload

Modular Injector Generation

I really wanted to kill the "monolithic loader" problem. You know the one, where every execution primitive is hardcoded into one giant file, and changing a single syscall means you have to touch every other part of the project.

In Stubcore, injectors are modular. We treat prototypes like Lego bricks, appending them into a generated source file at build time.

C generated injector
fprintf(f, "#pragma code_seg(\".inject\")\n\n");
fprintf(f, "int injector(CONST PBYTE payload, CONST SIZE_T payloadsize) {\n");
append_file_to_stream(proto_path, f);

Once that source is generated, we link it into a tight, isolated PE. By using /FIXED to strip relocation data and /NODEFAULTLIB to keep out the CRT garbage, we end up with a very clean, predictable executable region. It’s the same concept I used in my previous shellcode post, but automated.

cmd linking
link.exe /ENTRY:injector /NODEFAULTLIB /FIXED /ALIGN:16 /SECTION:.inject,ER

Packing the Composite Payload

Managing an injector and a payload separately is a headache. To fix that, Stubcore mashes them together into a single "composite" blob.

The builder assembles this at build time, slapping the injector and payload together with their respective lengths. By encrypting the whole thing as a single unit with AES-GCM, we get built-in authentication. If anyone tampers with a single byte of that structure, the whole thing becomes cryptographically invalid. I’d much rather have a clean failure than a partial execution of something that’s been messed with.

C builder logic
memcpy(composite, &inj_be, 4);
memcpy(composite + 4, &pay_be, 4);
memcpy(composite + 8, injector, inj_len);
memcpy(composite + 8 + inj_len, payload, payload_len);

Runtime-Bound Encryption

This is what makes the project interesting. If you look at the binary, you won't find an encryption key. It simply isn't there.

Instead, the stub derives the key at runtime from its environment. It might pull a string from a command-line argument, a machine name, or a specific registry key, then hashes that data to create the AES-256-GCM key.

C key derivation
int getKey(unsigned char key_out[32]) {
    char MAC[256];
    IP_ADAPTER_INFO AdapterInfo[16];
    DWORD dwBufLen = sizeof(AdapterInfo);
    DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
    if (dwStatus != ERROR_SUCCESS) {
        return 0;
    }
    PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
    while (pAdapterInfo) {
        if (pAdapterInfo->AddressLength == 6) {
            sprintf(MAC, "%02X%02X%02X%02X%02X%02X",
                pAdapterInfo->Address[0],
                pAdapterInfo->Address[1],
                pAdapterInfo->Address[2],
                pAdapterInfo->Address[3],
                pAdapterInfo->Address[4],
                pAdapterInfo->Address[5]);
            break;
        }
        pAdapterInfo = pAdapterInfo->Next;
    }
    SHA256((unsigned char*)MAC,
        strlen(MAC),
        key_out);
    return 1;
}

The execution gaurdrails serve multiple purposes. On one hand, it helps us protect our tactics, techniques, and procedures from our adversaries, and on the other it allows us to be very precise in our targeting. We don't need to hack the world, we need to hack that guy.

The Self-Writing Stub

To tie it all together, the final stub is generated on the fly. During the build process, Stubcore emits a custom stub.c. It literally writes the hex bytes of the encrypted payload into the source code, alongside the validation logic and the runtime key derivation code.

C payload embedding
fprintf(f, "unsigned char payload[] = {");
for (int i = 0; i < blob_len; i++)
    fprintf(f, "0x%02x,", blob[i]);

This turns the stub from a simple "loader" into a "runtime policy engine." It’s a much more interesting way to build software, and it makes the resulting binary a lot harder to reason about from a defender's perspective.

What's Next?

The "To-Do" list is still pretty long. I'm looking into direct syscall prototypes, entropy shaping to make the binaries look less suspicious, multiple environmental guards used in tandem, and distributed payload structures where the pieces live in different places.

The best part is that because the architecture is modular, I can add these features without performing major surgery on the core logic. It was built to be played with, and experimenting with a new execution method is now just an afternoon's work rather than a week-long project.

Closing Thoughts

At the end of the day, Stubcore was a lesson in what generated runtime execution could look like. The payload itself was never the interesting part, the interesting parts were the runtime dependencies, the modular injectors, and the way the execution logic builds itself.

Static loaders are easy to fingerprint because they never change. Something that generates its own behavior at build time and hunts for its keys at runtime is a different category of problem entirely.

Anyway, it was a blast to build and I learned a ton. That’s the only bar that matters.

The code is on GitHub if you want to poke around or tell me where my C-code is ugly: stubcore on GitHub.


← Back to Writeups Next Post →