.NET Code Injection
In this post I'll show how you can inject your own .NET code into a process that is already running the .NET framework.
I needed to do this when developing for Windows Media Center because there is no official documented way to determine what page is currently being displayed in Media Center.
I wanted to know when the user is showing the Media Center "now playing" page.
Media Center addins run in a completely separate process to the main Media Center process, which is ehshell.exe. Calls you make in your addin are remoted across to the Media Center ehshell.exe process.
The solution works using:
- an "Injecter" C# class which uses CreateRemoteThread to load a Bootstrap DLL into the ehshell.exe process
- The "Bootstrap" C++ DLL which is injected into the ehshell.exe process uses the CLR Hosting API to attach to the default .NET domain, and then load an instance of the "Injectee" C# class
- The "Injectee" C# class is loaded by the "Bootstrap" DLL and runs within the ehshell.exe process. It uses reflection to subscribe to an event within the ehshell.exe process and then sends an interprocess message when the event fires </ul>
The "Injecter"
Using Reflector, I looked around at the ehshell.exe assembly, and related assemblies, and eventually discovered that there is a PageChanged event fired by the ServiceBus.UIFramework.PageBasedUCPService class:
What is more, there is a static PageBasedUCPService.DefaultUCP method to return a PageBasedUCPService instance.
There are of course a couple of problems. The first problem being that these classes are internal to the ehshell assembly, the second issue being that these are all running in a totally separate process to my addin.
I searched a little, and came across this example showing how you could use the CreateRemoteThread from .NET to inject your own native code into a remote process.
Essentially it opens a handle to the remote process, finds the LoadLibrary system call's address in the remote process, allocates a chunk of memory in the process, and fills the memory with a LoadLibrary call to load a DLL that you specify, and then creates the thread in the remote process, passing the address of the chunk of memory as the starting point for the thread.
I recommend taking a look at the CRT call in the example. I'll not be duplicating the code here.
To use the CRT call, I first find Media Center:
public static Process FindMediaCenterProcess() { // Could be more than one if an extender is running too foreach (Process process in Process.GetProcessesByName("ehshell")) { if (process.SessionId == Process.GetCurrentProcess().SessionId) { return process; } } return null; }
I can then call the CRT method:
Process mediaCenterProcess = FindMediaCenterProcess(); IntPtr hwnd; string error; CRT(mediaCenterProcess, dllPath, out error, out hwnd);
The dllPath is the full path to my C++ DLL, in my case this was
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
@"\Atadore\MceFM\InjectBootstrap.dll"
The "Boostrap DLL"
The InjectBootstrap.dll is a pretty simple C++ library project. The DllMain calls a bootstrap function when the process is loaded:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Bootstrap(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
#include "stdafx.h" #include#include "objbase.h" #include "MSCorEE.h" #import "C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.tlb" raw_interfaces_only using namespace mscorlib; void Bootstrap() { OutputDebugString(L"MceFM Bootstrap Started"); CoInitializeEx(0, COINIT_MULTITHREADED ); ICorRuntimeHost* pICorRuntimeHost = 0; HRESULT st = CoCreateInstance(CLSID_CorRuntimeHost, 0, CLSCTX_ALL, IID_ICorRuntimeHost, (void**)&pICorRuntimeHost); if(!pICorRuntimeHost) return; // Clean up and log errror ... HDOMAINENUM hEnum = NULL; pICorRuntimeHost->EnumDomains(&hEnum); if(!hEnum) return; // Clean up and log errror ... IUnknown* pUunk = 0; st = pICorRuntimeHost->NextDomain(hEnum, &pUunk); if(!pUunk) return; // Clean up and log errror ... _AppDomain * pCurDomain = NULL; st = pUunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCurDomain); if(!pCurDomain) return; // Clean up and log errror ... _bstr_t assemblyName = "Last, Version=1.1.0.0, Culture=neutral, PublicKeyToken=792d614cdf38e9ce"; _bstr_t typeName = "MceFM.Last.Inject.Injectee"; _ObjectHandle* pObjectHandle = 0; pCurDomain->CreateInstance(assemblyName, typeName, &pObjectHandle); }</pre></div> The "Injectee"
Finally we come to the Injectee class, which is loaded by the DLL, and runs inside the remote (ehshell.exe) process. It is in a strongly named assembly which is stored in the GAC, so that the bootstrap call above can find it (note the strong name in the assembly name bstr).
I use a static constructor (which runs as soon as the class is loaded). In this static constructor I subscribe to the event:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Reflection; using System.Reflection.Emit; using System.Threading; namespace MceFM.Last.Inject { public class Injectee { static Injectee() { try { Assembly ehshell = Assembly.Load("ehshell"); // Get the PageBasedUCPService type Type pageBasedUCPServiceType = ehshell.GetType("ServiceBus.UIFramework.PageBasedUCPService"); // Call static DefaultUCP method to get a PageBasedUCPService instance const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty; object[] args = new object[0]; object defaultUCP = pageBasedUCPServiceType.InvokeMember("DefaultUCP", bindingFlags, null, null, args); // Get a reference to the PageChanged event EventInfo pageChangedEventInfo = pageBasedUCPServiceType.GetEvent("PageChanged"); Type pageChangedDelegate = pageChangedEventInfo.EventHandlerType; // Get the MethodInfo for the method to be called when the event fires MethodInfo newPageHandlerMethodInfo = typeof(Injectee).GetMethod("NewPageHandler"); // Build an array of types used for new method we shall dynamically emit Type pageType = ehshell.GetType("ServiceBus.UIFramework.Page"); Type[] pageChangedDelegateParameters = new[] { pageType }; // Need a dynamic method because we can't create a method that has // the Page type as a parameter DynamicMethod dynamicHandler = new DynamicMethod("", null, pageChangedDelegateParameters, typeof(Injectee)); ILGenerator ilgen = dynamicHandler.GetILGenerator(); ilgen.Emit(OpCodes.Nop); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Call, newPageHandlerMethodInfo); ilgen.Emit(OpCodes.Nop); ilgen.Emit(OpCodes.Ret); if(pageChangedDelegate != null) { // Subscribe to the event Delegate dEmitted = dynamicHandler.CreateDelegate(pageChangedDelegate); pageChangedEventInfo.GetAddMethod().Invoke(defaultUCP, new object[] {dEmitted}); } } catch(Exception ex) { Trace.TraceError("Unexpected error in Injectee initializer: {0}", ex); Trace.TraceError(ex.StackTrace); } } // Queue of new pages that have been navigated to. Decouples event handler // from re-despatching of the event to the addin process that is told // of the new event. private static readonly Queue<string> newPages = new Queue<string>(); // Indicates whether background thread that despatches events is running private static bool notifierActive; // Called when Media Center navigates to a new page public static void NewPageHandler(object page) { Trace.TraceInformation("New Page: " + page); lock(newPages) { if(!notifierActive) { Thread thread = new Thread(NewPageNotifier) {IsBackground = true}; thread.Start(); notifierActive = true; } newPages.Enqueue(page.GetType().FullName); Monitor.Pulse(newPages); } } // Thread that despatches events to addin process telling it that // a new page has been navigated to private static void NewPageNotifier() { while(true) { string page; lock(newPages) { while(newPages.Count == 0) { Monitor.Wait(newPages); } page = newPages.Dequeue(); } try { // Use whatever interprocess notification mechanism you wish. // WCF could be good. Here I use a simple web call. My // addin has a web server in it to receive these calls. WebClient webClient = new WebClient(); webClient.QueryString[Server.PAGE_QUERY_STRING] = page; webClient.DownloadString(Util.LocalBaseUrl + Server.MCE_PAGE_CHANGED_ACTION); } catch (Exception ex) { Trace.TraceError("Error while sending new page notification: {0}", ex); } } #pragma warning disable FunctionNeverReturns } #pragma warning restore FunctionNeverReturns } }The Result
In my web server running within my addin (which is notified by the "NewPageNotifier" method above), I keep track of which is the current page, and log information when the page changes:
Server notified of page change MediaCenter.Audio.AudioNowPlayingPage Server notified of page change MediaCenter.Audio.AudioNowPlayingTracklistPageAll this means that if you are a Last.fm user and want to tell Last.fm that you love a track that is currently playing then you can press the right-button on your remote control on the "now playing" page using my MceFM addin.
I use low level system hooks to know when you press the right button, and the above technique to know that you are on the "now playing" page:
Word if warning. If/When Media Center changes its internal structure this technique will completely fall over. Use sparingly, entirely at your own risk, to do the seemingly impossible.