We've built a functional C2. Now we need to make it invisible. In this final post, we'll explore two advanced topics: Beacon Object Files (BOFs) and Sleep Obfuscation.
Beacon Object Files (BOFs): The Scalpel
Using .NET is great, but it's heavy. It loads the CLR, which is a massive indicator. Sometimes you want to be surgical.
What is a BOF?
Introduced by Raphael Mudge for Cobalt Strike in 2020, a BOF is just a C program that has been compiled but not linked. It's an .o file (Object File).
Normally, when you compile a program, the "Linker" connects all the function calls (like printf) to the libraries that contain them. A BOF leaves these disconnected. It's a bag of code with a table of "Relocations" (To-Do notes).
The BOF Loader Our agent acts as the Linker. When we send a BOF:
- Allocate: We carve out a small chunk of memory (RWX or RX).
- Resolve Symbols: The BOF calls functions like
kernel32$VirtualAlloc. We parse these strings, load the library, find the function address, and patch the call site. - Apply Relocations: The code assumes it lives at address 0. We update all memory references to match the actual allocated address.
- Execute: We jump to the entry point.
Why is this stealthy?
- No Process Creation: It runs inside our own process (unlike Fork & Run).
- No New DLLs: It uses what's already there.
- Tiny Footprint: A BOF to list processes might be 2KB.
We can now use the massive ecosystem of community BOFs (like TrustedSec's CS-Situational-Awareness-BOF) directly in our custom agent.
Ekko: Sleeping with One Eye Open
The most dangerous time for malware is when it's doing nothing. Most agents spend 99% of their time Sleep()ing.
While sleeping, the agent is just a sitting duck in memory. EDRs can scan memory looking for strings or byte signatures.
The Solution: Sleep Obfuscation We want to encrypt our own memory while we sleep, and decrypt it only when we wake up. But we can't just write a loop to do this, because if you encrypt your own code, the CPU can't execute the next instruction!
We implemented Ekko, a technique originally published by C5pider (based on concepts from Peter Winter-Smith at MDSec).
The ROP Chain Trick
We use Return Oriented Programming (ROP). We don't run our own code; we borrow snippets of code from Windows DLLs.
We use the Windows Timer Queue API (CreateTimerQueueTimer) to schedule a series of API calls to happen in the future. We are effectively programming the OS to protect us.
The Chain:
EventSet(StartEvent): Signals that the timer has fired.VirtualProtect(RW): Marks our own code section as Read-Write. This is critical. If we leave it Executable, the EDR might catch us modifying it. By making it RW, we hide it from "Executable Memory" scans.SystemFunction032(Encrypt): We use this undocumented function (part ofadvapi32.dll) to encrypt our memory image using RC4.WaitForSingleObject(Sleep): The actual delay. The thread pauses here.SystemFunction032(Decrypt): We decrypt our memory.VirtualProtect(RX): We restore Read-Execute permissions so we can run again.EventSet(EndEvent): We wake up the main thread.
Stack Spoofing
Ekko also handles Call Stack Spoofing.
When the thread is sleeping at step 4, an analyst looking at the call stack will see a legitimate chain of Windows Timer functions, originating from ntdll.dll. They won't see MyMalware.exe -> Sleep.
// spoof.cpp
CtxSpoof.Rsp -= sizeof(PVOID); // Adjust stack pointer
SetThreadContext(Thread, &CtxSpoof);
Conclusion
Building C24U has been a journey from "Hello World" to a usable evasive C2.
- Polymorphic Network Layer: HTTP/DNS/SMB (WinINet & Custom DNS Tunneling).
- Evasion: Indirect Syscalls with Custom Hashing & Junk Assembly.
- Weaponization: Named Pipe Redirectors for PPID Spoofing.
- Post-Ex: In-Memory .NET & BOF Loader.
- Stealth: Ekko Sleep Obfuscation.
This is it. Thats all for now, but I plan to continue working on this project and add more features in the future. Huge Thanks to Maldev Academy for a lot of the resources and inspiration. Building this was a blast.