2006-08-27

Initial Experience with Detours from Microsoft Research

Previously, I had been considering techniques that would allow me to RDP into a system and still be able to use my U3 Flash drive. I didn't want to have to log into a session on the glass, and then plug in the password for the U3 Launch Pad program. I want to plug the drive into the system in my office, and be able to hit it when I want to. I'm lazy.

Ultimately, I chose to explore Detours from Microsoft Research to accomplish this. I'm happy to report a rather simple, if somewhat unelegant, success. I simply downloaded Detours Express 2.1, explored the documentation a bit, and played with the samples. In under 2 hours, I had accomplished what I had set out to do - essentially make ProcessIdToSessionId return the same thing as WTSGetActiveConsoleSessionId.

Two of the Detours samples ("WithDll" and "Simple") were used in my solution. WithDll was just what I needed to jumpstart the LaunchU3.exe process with the code I wanted to "inject" into the process, so I was able to use that sample with absolutely no modifications.

The "Simple" sample provided framework code that I simply adapted to do what I wanted. Again, I was looking to "replace" ProcessIdToSessionId's implementation with:
*pSessionId = WTSGetActiveConsoleSessionId();

The code at the bottom of this post uses Detours to help me do just that. The code was written in Visual Studio 2005 / VC++ 8.0, so some of the CRT functions are the "Secure (_s)" versions. I didn't go through and do the _CRT_INSECURE_DEPRECATE #ifdefs.

So anyway, this works great, but normally the LaunchU3.exe program runs automatically when the USB drive is plugged into a system - a small partition presents itself as a read-only drive formatted with the CDFS filesystem. That partition contains an AutoRun.inf instructing Windows to kick off LaunchU3.exe. But I need to run WithDll.exe from the Detours Express package, not LaunchU3.exe.

Sounded like a killer application for an image file hijack ala "Image File Execution Options: Good, Evil, Fun":
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LaunchU3.exe\Debugger = "<path to>\WithDll.exe /d:<path to>\MyDetoursDll.dll "

Note the space before the closing quote of the value - the Debugger gets passed the image name, so the command to WithDll.exe looks like:
<path to>\WithDll.exe /d:<path to>\MyDetoursDll.dll x:\LaunchU3.exe

The only problem is that we do actually want to launch the specified program (LaunchU3.exe). But running LaunchU3.exe will kick off WithDll.exe, which will start LaunchU3.exe, which will start WithDll.exe... There are a couple of options to deal with this. The easiest is to copy LaunchU3.exe from the USB drive onto the hard drive and give it a new name - this was the approach that I took. Then, specify the following as the Debugger command in Image File Execution Options:
<path to>\WithDll.exe /d:<path to>\MyDetoursDll.dll <path to>\MyLaunchU3.exe

Another way would be to turn WithDll into a debugger - change the flags that get passed to the Detours function "DetourCreateProcessWithDll" to specify "DEBUG_ONLY_THIS_PROCESS", and incorporate a dummy debugger loop into the application, something like:
DEBUG_EVENT de = {0};
for( ;; )
{
WaitForDebugEvent( &de, INFINITE );
if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode ) break;
ContinueDebugEvent( de.dwProcessId, de.dwThreadId, DBG_CONTINUE );
}

When a process calls CreateProcess with DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS specified in dwCreationFlags, the Debugger value of Image File Execution Options is not checked (makes sense, right? That's what the "Debugger" value is for). Thus, WithDll.exe can launch LaunchU3.exe without having LaunchU3.exe launch another instance of WithDll.exe. Launcherrific.

The last option is to modify the contents of the small CDFS partition on the U3 drive. This would allow one to toss the Detours programs (WithDll.exe and dependencies, as well as the "payload" DLL - a modified "Simple.dll" in this case) right onto the USB drive. Modify AutoRun.inf to kick off WithDll.exe with the right parameters, and the solution is clean and self-contained. Perhaps I will experiment with this... When I get more time =8->

Code for the Detours payload DLL:
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // for WTSGetActiveConsoleSessionId
#endif

#include <stdio.h>
#include <windows.h>
#include "detours.h"

static BOOL (WINAPI* TrueProcessIdToSessionId)
( DWORD dwProcessId, DWORD* pSessionId ) = ProcessIdToSessionId;

static const unsigned short c_usLogTimeBufLen = 40;
static const unsigned short c_usMsgBufLen = 980;
static const unsigned short c_usFullMsgBufLen =
c_usLogTimeBufLen + c_usMsgBufLen;
static const char* const c_pszModName = "U3Detours";

void DebugPrint( const char* const pszFormat, ... )
{
char szNowTime[c_usLogTimeBufLen + 1] = {0};
SYSTEMTIME st = {0};
::GetLocalTime( &st );
_snprintf_s( szNowTime, c_usLogTimeBufLen, c_usLogTimeBufLen,
"%04d-%02d-%02d %02d:%02d:%02d.%03d", st.wYear, st.wMonth,
st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds );

va_list args;
va_start( args, pszFormat );
char szMsg[c_usMsgBufLen + 1] = {0};
_vsnprintf_s( szMsg, c_usMsgBufLen, c_usMsgBufLen, pszFormat, args );
va_end( args );

char szFullMsg[c_usFullMsgBufLen + 1] = {0};
_snprintf_s( szFullMsg, c_usFullMsgBufLen, c_usFullMsgBufLen,
"%s ==> %s(TID=%d): %s\n", szNowTime, c_pszModName,
GetCurrentThreadId(), szMsg );

OutputDebugString( szFullMsg );
}

BOOL WINAPI InterceptProcessIdToSessionId( DWORD dwProcessId,
DWORD* pSessionId )
{
BOOL bSuccess = TrueProcessIdToSessionId( dwProcessId, pSessionId );
if( !bSuccess )
{
DebugPrint( "ProcessIdToSessionId for PID %d failed with %d",
dwProcessId, GetLastError() );
}
else
{
DWORD dwActiveConsoleSessionId = WTSGetActiveConsoleSessionId();
DebugPrint( "ProcessIdToSessionId for PID %d succeeded; "
"SessionId %d intercepted and being set to %d",
dwProcessId, *pSessionId, dwActiveConsoleSessionId );
// This is all we're really looking to do...
*pSessionId = dwActiveConsoleSessionId;
}
return bSuccess;
}

BOOL APIENTRY DllMain( HMODULE hMod,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
(void)hMod;
(void)lpReserved;
LONG lErr = 0;

if( DLL_PROCESS_ATTACH == ul_reason_for_call )
{
DebugPrint( "Starting..." );

DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourAttach( &(PVOID&)TrueProcessIdToSessionId,
InterceptProcessIdToSessionId );
lErr = DetourTransactionCommit();
if( NO_ERROR == lErr )
{
char szExe[MAX_PATH + 1] = {0};
GetModuleFileName( NULL, szExe, MAX_PATH );
DebugPrint( "Detoured ProcessIdToSessionId in PID %d(%s)",
GetCurrentProcessId(), szExe );
}
else
{
DebugPrint( "Error detouring ProcessIdToSessionId: %d", lErr );
}
}
else if( DLL_PROCESS_DETACH == ul_reason_for_call )
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach( &(PVOID&)TrueProcessIdToSessionId,
InterceptProcessIdToSessionId );
lErr = DetourTransactionCommit();

DebugPrint( "Removed ProcessIdToSessionId: %d", lErr );
}

return TRUE;
}

1 comment:

Jagz said...

Really good post on detours. I want to use it for sending keypresses through VC++ script to a game. Is it possible ?