When experimenting with low level code, trampolines quickly peep out.
To make the long story short, it's a nice technique which can be used to make some function transparently (to the caller) execute other pieces of code.
It is a technique so powerful (and neat) that it is widely used in fundamental issues (library calls, to make an example, where - in the ELF executable format - got and plt tables are used to dynamically relocate code after the actual process has been started, usually according to some sort of lazy load policy).
Much more interesting is the possibility to use trampolines to dynamically (i.e., at runtime) modify some process, to hijack its execution and make it do something which it was not intended to do when it was written. Several mature tools exist to help achieve this task (like DynamoRIO), and even some simpler tools like DYNINST or a rule-based instrumentor which I'm working on since 2 years (and which will be released, eventually).
The Story
Some time ago, I was working on a Windows project which involved reading text which was being typed into any application, to suggest completion/replacement of the sentences according to some user-specified rules. At the design stage, the biggest problem was: how can I actually read text which is being typed?
The immediate attempt was to open up Spy++ and try to intercept some text from a third party application.
As this showed to be viable, I tried to do the same programmatically. The attempt was to use the WM_GETTEXT message to kindly ask the other application to give me, mr. Unknown, it's internal text:
HWND hWindow; char param[1000];
int count;
count = SendMessage(hWindow, WM_GETTEXT, 0, (LPARAM) param);
where hWindow was previously set to the window's handle, in a way which is out of the scope of this post.
Guess what? It worked! Too bad, after the initial excitement, this method quickly drew a blank. As Spy++ revealed, only standard Win32 objects were silly enough to give their text out to anybody.
Empty text. That's what. As I realized, more recent components, or even custom ones, are allowed to do whatever they want. Even returning an empty string when asked to do with WM_GETTEXT. "I don't contain any text, I swear!"
What all this has to do with trampolines?
Quick answer: I decided to take (as usual) the low-level approach.
The idea was: if something has to be written on screen, then it has to be passed to the graphic card. So, why cannot I intercept calls to the routines involved in this task and get the text from there?
Using trampolines was the right way to face this task. Unfortunately, writing all this by hand is an enormous time-consuming operation, which involves writing some assembly code, disassembling portions of the executable at runtime, playing around with bits and bytes, and skip several lunches.
Even though this was a challenging problem, and this is very similar to what I do every day in my life (yes, skipping lunches as well), days have only 24 hours (at least so far). No chance at completing this project in finite time.
Microsoft is Open Source
So what? Was I fooled?
As usual, i fired up my browser and asked some questions to the ol' friend Google. What it came out was a page on Microsoft Research. There was a very nice project, called Detours, which was exactly what I was looking for: a library to instrument arbitrary Win32 functions.
As the project description says: "Detours intercepts Win32 functions by re-writing the in-memory code for target functions. The Detours package also contains utilities to attach arbitrary DLLs and data segments (called payloads) to any Win32 binary".
This was just what I needed! And - miracle! - it was free, and its source code was available! I checked twice: yes, it was from Microsoft Research!
After healing myself from the sudden faint, I started inspecting the code: it is a trampoline-based library, which is able to inject at runtime hooks to intercept any function, provided you know its signature when writing the code. I compiled the sources, produced the dll, and started messing around with it.
(as a sidenote: now there is a newer version which is able to handle 64 bits code and provides some additional features. And yes, to get it, you have to pay. Nothing has really changed out there...)
Let's rewrite Windows
The application I was working on came out nicely. But it is a bit complex, and not very suited to showing the potential of Detours easily. So, let's take another direction and try some code to replace every piece of text in the system with a single word: -PolyTime-.
This was done by detouring the various text-drawing APIs provided by Windows and wrapping them within new versions. And this was done with really a few lines of code. It's harmless, in the sense that no data will be corrupted. But once the code is started, the only way to rollback the state of the system is to... restart it!
To create a system-wide text hijacker, the few functions we have to wrap are the following:
Which are clearly a set of different functions designed to do almost the same thing, according to the KISS principle.
GDI Hooking
What we are doing here is fairly simple. Whatever application is willing to paint text on the screen, has to invoke the aforementioned functions. The code we are going to write in a few lines will actually do the following:
Intercept the call to the original function provided by Windows' Graphic Device Interface (GDI), by (transparently) interposing our own function;
Replace the text the application is willing to write on screen with our special one;
Call the original Windows function so that our text is actually written on screen.
To do this system-wide, we can develop a dll using detours and inject it in every application, using a technique similar to the one which I discussed some time ago in a previous post.
So, let's start and create a dll which injects itself in any application which is getting the focus:
#pragma data_seg(".SHARDAT") static HHOOK hkb = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.SHARDAT,RWS")
LRESULT __declspec(dllexport)__stdcall CALLBACK FocusProc(int nCode, WPARAM wParam, LPARAM lParam) {
return ::CallNextHookEx(hkb, nCode, wParam, lParam);
}
What we do now, is to detour all the functions of interest upon loading of the dll. This is done in several steps.
First of all, we have to keep in mind that we have to replace the text using the original functions. To do so, we define the string we want to replace and some function pointers to the original functions (respecting their signatures):
// String we want to replace
TCHAR *new_text = _T("-PolyTime-");
// Function pointers to the original (un-detoured) text APIs
int (WINAPI *Real_DrawText)(HDC a0, LPCWSTR a1, int a2, LPRECT a3, UINT a4) = DrawTextW;
BOOL (WINAPI *Real_ExtTextOut)(HDC hdc, int X, int Y, UINT options, const RECT *lprc, LPCWSTR text, UINT cbCount, const INT *lpSpacingValues) = ExtTextOutW;
int (WINAPI *Real_DrawTextEx)(HDC hdc, LPCWSTR lpchText, int cchText, LPRECT lprc, UINT dwDTFormat) = DrawTextW;
BOOL (WINAPI *Real_PolyTextOut)(HDC hdc, const POLYTEXT *pptxt, int cStrings) = PolyTextOutW;
LONG (WINAPI *Real_TabbedTextOut)(HDC hDC, int X, int Y, LPCWSTR lpString, int nCount, int nTabPositions, const INT *lpnTabStopPositions, int nTabOrigin) = TabbedTextOutW;
BOOL (WINAPI *Real_TextOut)(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cchString) = TextOutW;
At this point, we write the main routine of the dll which calls detours to hijack the calls. In addition, it creates a focus hook, so that this dll (and therefore this routine) will be executed in every application, thus detouring GDI APIs for every process in the system!
#include "detours.h"
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// Initialize detours DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText);
DetourAttach(&(PVOID&)Real_ExtTextOut, Mine_ExtTextOut);
DetourAttach(&(PVOID&)Real_DrawTextEx, Mine_DrawTextEx);
DetourAttach(&(PVOID&)Real_TabbedTextOut, Mine_TabbedTextOut); DetourAttach(&(PVOID&)Real_TextOut, Mine_TextOut);
DetourTransactionCommit();
// Focus hook, to get into other processes and process keystrokes
hkb = SetWindowsHookEx(WH_CBT, (HOOKPROC)FocusProc, hinstDLL, 0);
break;
case DLL_PROCESS_DETACH:
// Uninitialize Detours DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)Real_DrawText, Mine_DrawText);
DetourDetach(&(PVOID&)Real_ExtTextOut, Mine_ExtTextOut);
DetourDetach(&(PVOID&)Real_DrawTextEx, Mine_DrawTextEx);
DetourDetach(&(PVOID&)Real_TabbedTextOut, Mine_TabbedTextOut);
DetourDetach(&(PVOID&)Real_TextOut, Mine_TextOut);
DetourDetach(&(PVOID&)Real_TextOut, Mine_TextOut);
DetourTransactionCommit();
// Remove the focus Hook
UnhookWindowsHookEx(hkb);
break;
}
return TRUE;
}
As you can see, this code uses DetourAttach() to "swap" functions using pointers. Every time an application calls, let's say, DrawText(), an actual call to Mine_DrawText() will be issued, but the original DrawText() will still be accessible through our function pointer Real_DrawText()!
Now it is just a matter of writing the Mine_* functions, fooling the Real_* ones into thinking that the text the application asked to paint is our one. This is a repeating task, in the sense that all Mine_* function are very similar, albeit they have to respect the syntax of the Real_* ones, so as an example I'm putting here just the code for Mine_DrawText():
int WINAPI Mine_DrawText(HDC hdc, LPCWSTR text, int nCount, LPRECT lpRect, UINT uOptions) {
if (!text) return TRUE;
return Real_DrawText(hdc, new_text, 10, lpRect, uOptions);
}
What this function does is simple: it ignores text and nCount, passing our string and its length! That's it. A walkover.
Now, to load the dll, a simple launcher can be written, which load the dll by hand, and simply waits for it to be disseminated into the system.
Here you can find some files related to this project:
- A working example: run it at you own risk! :)
- The full source code of the dll
Now take the spray, and start painting Windows!
PS: If you try this code in Vista or newer versions, you might find out that not all the text is replaced. This is because Microsoft has replaced GDI with DirectWrite and Direct2D. So, while older applications can still rely on GDI, newer ones and the systems themelves use different APIs. In order to extend this project, what you have to do is to detour the additional functions as well!
Someone asked me to publish a video of this software in action. Here it is:
ReplyDeletehttp://www.youtube.com/watch?v=3e7YgdpInxo
(the quality is terrible!)
The video is unavailable in youtube
ReplyDelete