A trick to extend the un-extendable

by acha11 8. May 2009 01:29

Update 2009-10-29:

iRacing have implemented a check to restrict use of this technique... more here.

 

This post’s about a nice trick I came across to inject your own code into a third party app that’s not designed to be extensible.

The app in question is iRacing, a racing simulator. The extension is Stephane’s Telemetry HUD Plugin, which adds extra timing and telemetry info as a HUD on top of the standard 3d view that iRacing renders.

iRacing without HUD

iRacing old-school

 

iRacingSim with HUD

iRacing + Telemetry HUD (note the lap times in the top-left and RPMs on the top right)

The Telemetry HUD Plugin ships as a single DLL, d3d9.dll, which you copy into your main iRacing program folder (the folder which contains iRacing.exe).

When I first saw this arrangement, I assumed the trick worked like this:

  1. iRacing asks to load the “real” Direct3D d3d9.dll.
  2. Windows applies its defined search orderfor DLLs, and decides to load Stephane’s d3d9.dll (since it’s in the local app folder), instead of the “real” dll that’s off in %windir%\system32.
  3. Stephane’s d3d9.dll somehow (maybe by calling LoadLibrary? Hey, I’m not Stephane, alright?) causes the real d3d9.dll to be loaded as well so that iRacing can still get at the Direct3D functions it needs. (But what about naming collisions? Is it even possible to have two DLLs with the same name loaded? Answers below…)

I wanted to find out whether my hunch was on the right track, and how the details really worked. So first I fired up Dependency Walker and loaded iRacingSim.exe (the main iRacing executable) while the plugin was installed:

image

The top-left panel lists the DLLs that iRacingSim.exe depends on. I’ve highlighted D3D9.DLL – notice that it’s loaded from the iRacing folder, since I’ve got the Telemetry HUD Plugin installed, and Dependency Walker is smart enough to apply the same DLL resolution rules as Windows.

Because I’ve highlighted D3D9.DLL, the second-from-the-top-on-the-right panel is listing all the functions it exports, and the top-right panel is listing just those exported functions which are actually called by iRacingSim.exe. We can see that D3D9.DLL only exports Direct3DCreate9(), and that’s the only function which iRacingSim.exe actually calls. Contrast that with a view of the “real” D3D9.dll:

image

Here you can see that the real D3D9.DLL exports a whole bunch of functions (in the panel second from the top on the right), but Dependency Walker still says that only one of them is actually called by iRacingSim.exe.

So at this point, it looks to me as though Stephane either:

  1. chose to fake a DLL that iRacingSim.exe called very few exports from (saving time and effort); or
  2. chose D3D9.DLL because a call to Direct3DCreate9() was a convenient time to hook in and do his own initialisation (since by that point, he knows that the graphic subsystem is being initialised).

One question remains: When iRacing calls Direct3DCreate9() in what it thinks is Direct3D but is actually Stephane’s fake DLL, how does Direct3D actually get initialised? Does Stephane just pass the call through to the real D3D9.DLL? Fire up Dependency Walker and load the Telemetry HUD Plugin d3d9.dll:

image

Well, we can tell from the tree at the top left that Dependency Walker can’t see a dependency from the fake D3D9.DLL to the real one. Maybe Dependency Walker can only see static, declared (declarative) inter-DLL dependencies, though – perhaps if Stephane’s code imperatively sets up the dependency by calling LoadLibrary, passing a path to the real D3D9.dll, he can then call to the real thing? Maybe we could check that by checking the string table in his DLL?

Fire up devenv.exe and open the fake D3D9.dll using the binary editor. Ctrl-F to look for occurrences of “d3d9” – maybe we can find some clue as to how the real D3D9.dll gets loaded?

image

That’s a pretty interesting string – “\d3d9.dll”. And immediately before and after it, there’s talk of something called PROXYDLL, which looks a bit library-ish. Google is our friend, and finds us “Intercept Calls to DirectX with a Proxy DLL”. That’s an article on CodeGuru, including sample code, which uses a pretty familiar-sounding technique to inject code into a Direct3D 8 or 9 app and render extra stuff on the screen.

After downloading the sample source, I run into the following chunks of code…

void InitInstance(HANDLE hModule)
{
    OutputDebugString("PROXYDLL: InitInstance called.\r\n");

Well, given that “PROXYDLL: InitInstance called.” shows up in the binary screenshot above character for character, I’m pretty sure that Stephane’s work’s based off this sample, so we can be confident the D3D9.dll technique used by the sample’s also used by Stephane’s fake D3D9.dll.

// Exported function (faking d3d9.dll's one-and-only export)
IDirect3D9* WINAPI Direct3DCreate9(UINT SDKVersion)
{
    if (!gl_hOriginalDll) LoadOriginalDll(); // looking for the "right d3d9.dll"

Here we go, how does LoadOriginalDll() work?

void LoadOriginalDll(void)
{
    char buffer[MAX_PATH];
    // Getting path to system dir and to d3d8.dll
    ::GetSystemDirectory(buffer,MAX_PATH);

    // Append dll name
    strcat(buffer,"\\d3d9.dll");
    // try to load the system's d3d9.dll, if pointer empty
    if (!gl_hOriginalDll) gl_hOriginalDll = ::LoadLibrary(buffer);

    // Debug
    if (!gl_hOriginalDll)
    {
        OutputDebugString("PROXYDLL: Original d3d9.dll not loaded ERROR ****\r\n");
        ::ExitProcess(0); // exit the hard way
    }
}

And there’s the resolution (see what I did there?): a quick call to GetSystemDirectory tells us where system32 is, and we ask LoadLibrary to load d3d9.dll from that folder.

None of this was insanely complicated, but it’s something I wouldn’t have ever tried, I think.

For starters, I would have been sceptical that it’d be so easy to subvert the DLL resolution rules for what is effectively a system library. Having said that, I see how there’s no sensible measure to prevent a user running whatever code they want on their machine, of course.

Second, I’m really surprised that iRacing, an online multiplayer game (where you’d expect some cheat/hack countermeasures on the part of the developers), doesn’t have parasite detection code that picks up either unrecognised or suspicious-looking DLLs, or at least funky stuff like “hey, wait a sec, I have TWO d3d9.dlls loaded in my process space! uncool!”.

It’ll be interesting to see how iRacing respond to this plug-in. Traditionally, iRacing have patched their software only in the one week break between quarterly seasons; only the most egregious bug exploits (for example, cuttable corners on the track) have been patched “out-of-band”. Given that the inter-season break was last week, and that Stephane released his tool a few months back, it looks like they’ve chosen to tolerate his hack-let for now.

This tool is grey-hat. It surfaces information to the driver which otherwise isn’t available: extremely detailed and precise, real-time feedback on whether you’ve fallen behind or pulled ahead of your best lap ever up to this point on the current lap – incredibly useful, with overtones of mild cheatiness.

My concern is that it’s not that far from here to a genuine black-hat tool which observes your acceleration and accelerator levels, reasons that you must be spinning the rear wheels, and eases off the accelerator by filtering the values fed from your wheel and pedals to iRacing (using a similar technique to that found above).

That class of hacks would basically ruin the key characteristic of iRacing that makes it the greatest ever online racing league: a competitive and even playing field populated by (almost universally) non-cheating (mostly) non-idiots.

Comments

Comments are closed

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

RecentComments

Comment RSS