"Detours is a software package for re-routing Win32 APIs underneath applications. For almost twenty years, has been licensed by hundreds of ISVs and used by nearly every product team at Microsoft." [1]
Detours is a library for instrumenting arbitrary Win32 functions on Windows‑compatible processors. It intercepts Win32 calls by rewriting the target function’s machine code in memory. The Detours package also ships with utilities that let you attach any DLLs or data segments (so‑called payloads) to existing Win32 binaries.
- Clone the vcpkg package manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
- Build vcpkg itself:
./bootstrap-vcpkg.sh # .\bootstrap‑vcpkg.bat on Windows
- Let vcpkg integrate with your Visual Studio or CMake setup:
./vcpkg integrate install
- Install Detours:
vcpkg install detours
- Project → Project Properties → C\C++ → Additional Include Directories
- Project → Project Properties → Linker → Additional Library Directories
"Create a new process and load a DLL into it. Chooses the appropriate 32-bit or 64-bit DLL based on the target process"[2].
DetourCreateProcessWithDllEx
launches a new process in suspended mode (CREATE_SUSPENDED
), rewrites the executable’s in‑memory import table so that your DLL is listed first, and then resumes execution. When the process wakes up, the Windows loader brings in that DLL before loading any other imports and finally jumps to the program’s entry point.
-
The altered import table points at export ordinal #1 of your DLL (if that export is missing, the process fails to start)
-
If the bitness of parent and target processes differ (32‑bit ↔ 64‑bit), Detours briefly spins up a matching
rundll32.exe
helper so it can patch the target’s import table correctly. -
Once the DLL is loaded you can undo the patch by calling
DetourRestoreAfterWith
; the information needed for the rollback was copied in advance viaDetourCopyPayloadToProcess
.
BOOL DetourCreateProcessWithDllEx(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation,
_In_ LPCSTR lpDllName,
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW
);
lpApplicationName : Application name as defined for CreateProcess API.
lpCommandLine : Command line as defined for CreateProcess API.
lpProcessAttributes : Process attributes as defined for CreateProcess API.
lpThreadAttributes : Thread attributes as defined for CreateProcess API.
bInheritHandles : Inherit handle flags as defined for CreateProcess API.
dwCreationFlags : Creation flags as defined for CreateProcess API.
lpEnvironment : Process environment variables as defined for CreateProcess API.
lpCurrentDirectory : Process current directory as defined for CreateProcess API.
lpStartupInfo : Process startup information as defined for CreateProcess API.
lpProcessInformation : Process handle information as defined for CreateProcess API.
lpDllName : Pathname of the DLL to be insert into the new process. To support both 32-bit and 64-bit applications, The DLL name should end with "32" if the DLL contains 32-bit code and should end with "64" if the DLL contains 64-bit code. If the target process differs in size from the parent process, Detours will automatically replace "32" with "64" or "64" with "32" in the path name.
pfCreateProcessW : Pointer to program specific replacement for the CreateProcess API, or NULL
if the standard CreateProcess API should be used to create the new process.
Returns TRUE
if the new process was created; otherwise returns FALSE
.
See error code returned from CreateProcess.
bool SpawnWithDll(const std::wstring& exe)
{
STARTUPINFOW si{ sizeof(si) };
PROCESS_INFORMATION pi{};
// command line must be mutable buffer
std::wstring cmd = L"\"" + exe + L"\"";
BOOL ok = DetourCreateProcessWithDllExW(
exe.c_str(), // lpApplicationName
&cmd[0], // lpCommandLine
nullptr, nullptr,
FALSE,
CREATE_DEFAULT_ERROR_MODE,
nullptr, // inherit environment
nullptr, // inherit CWD
&si,
&pi,
"C:\\Users\\sample.dll", // <‑‑ the injected DLL
nullptr); // use default CreateProcessW
if (!ok) {
SetColor(FOREGROUND_RED);
std::wcerr << L"[!] DetourCreateProcessWithDllExW failed: "
<< GetLastError() << L"\n";
return false;
}
SetColor(FOREGROUND_GREEN);
std::wcout << L"[+] Process startet with injected DLL..." << L"\n";
std::wcout << L"[+] PID: " << pi.dwProcessId << L"\n";
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return true;
}
Now We Reach the Most Exciting Part: EDR Showdown
I tested the approach against:
- Microsoft Defender ATP (OS: Windows 11 – Bypassed)
- Palo Alto Cortex XDR (OS: Windows 11 – Bypassed)
- SentinelOne (OS: Windows 11 – Bypassed)
- Trend Micro Vision One (OS: Windows 10 – Bypassed)
Overall, none of the solutions raised an alert or blocked the activity.
Interestingly, Defender occasionally misbehaved whendllhost.exe
spawnedcmd.exe
runningwhoami
, but even then it didn’t detect the DLL injection itself.
The Defender event timeline showed no further anomalies, and the other EDRs behaved identically.
There’s clearly more potential here for further experimentation.
Detours_Defender_ATP.mp4
Screenshot:
Detours_Cortex.mp4
Screenshot:
Detours_SentinelOne.mp4
Screenshot:
Detour_TrendVisionOne.mp4
Screenshot:
[1] https://www.microsoft.com/en-us/research/project/detours/
[2] https://github.com/microsoft/detours/wiki/DetourCreateProcessWithDllEx