Introduction
Lately I've been doing some programmatic graffiti.

Maybe I have too much time on my hands, but sooner or later every programmer wonders:
- How to extract text displayed by other applications.
- How to draw your own text in the windows of external applications.
Maybe you've even wondered:
- How to hide the text displayed by other applications.
That's fun, too. Minimalism.

I like it.
But why stop with a single application? Let's replace every piece of text drawn anywhere on the system with our tag, creating a harmless "graffiti bomb". Scroll down if you're curious.
Why Are You Vandalizing Your Computer Screen?
To illustrate a technique.
In How I Built a Working Online Poker Bot, Part 4: The Poker Botting Erector Set, we looked at some code to extract text from the Poker Time online poker client software. That was relatively easy because Poker Time windows are, for the most part, standard Windows controls. That means the text they contain is wide-open, to our bot and a hundred other tools, such as Spy++.

Poker Stars and Full Tilt are a little different. Poker Stars and Full Tilt are special. Pull up Spy++ or a similar tool, point it at the Poker Stars game chat window. What do you see?

Empty text, that's what. Because the Poker Stars game chat window isn't a standard Windows control, with well-defined behavior. It's a custom window, likely implemented using MFC (we know this because of the characteristic "Afx:" window class). Custom windows are allowed to do whatever they want, within reason. Such as return an empty string in response to the WM_GETTEXT message.
"I don't contain any text! Scout's honor!"
Are we fooled?
Today I'm going to show you one way to extract this text. There are others. We'll leave those for future posts, or for your own investigations.
The Text Output Bottleneck
Almost every poker client features a game chat window which contains game actions, chat text, and dealer spam:

But sometimes getting the text out of this window is a little more complicated than just sending the window a WM_GETTEXT. The method we're going to discuss today is very similar to the method we use to snoop on 3rd-party file I/O. Only this time, instead of detouring the CreateFile/WriteFile/ReadFile functions, we're going to detour the various text-drawing APIs provided by Windows. DrawText and ExtTextOut in particular.
System-Wide Graffiti?
We're going to take advantage of the fact that, by and large, every piece of text drawn by the Windows operating system is routed through a small handful of public APIs. No, I mean virtually EVERY piece of text drawn by your Windows PC is routed through a small handful of functions that display text.
By detouring those functions and replacing them with our own versions (a matter of a few lines of code) we can do things like create a system-wide graffiti bomb. Below, I've replaced every piece of text drawn by every application on the system with a single phrase:
Coding the Wheel was here
This required tens, not hundreds, of lines of code.

Hey, did I mention that VIRTUALLY EVERY PIECE OF TEXT ON THE SYSTEM IS ROUTED THROUGH A HANDFUL OF TEXT-OUTPUT APIs?
Okay. I'm glad we got that straight.
Situated GDI Hooking
If you've been following the botting series, you already know about detouring text-output functions. But if you haven't, understand that whenever an application calls a system API like DrawText or ExtTextOut in order to paint text on the screen, it's going to be calling our code instead. Our code then does four things:
- Calls the original version of the function provided by the Windows operating system.
- Examines the HDC provided with the call, and gets the associated window as an HWND value. This tells us which window the text is being drawn to.
- Examines the location of the text, as provided by point or rectangle coordinates. This tells us what part of the window the text is being drawn to.
- Examines the text itself, and does something with it.
I like to call this "situated GDI hooking" because it's installs detours around text-drawing APIs provided by the Graphic Device Interface (GDI) and uses the device context (HDC) along with the X,Y location of the text to establish the meaning of the text: is this game chat text? Miscellaneous window text? Button text? And so forth.
Injecting the DLL
First, inject a DLL into the target application's process using any DLL injection mechanism.
This has been covered on the Internet, in books, and here on Coding the Wheel, endlessly, so I won't rehash it.
Just get your DLL into the target application's process by any means necessary.
Detouring the Text Output Functions
If you've used the Microsoft Detours library before, you already know the score on this one. For each function you want to detour, you'll need to create an equivalent version taking the same parameters and return type and place it in a DLL. Let's take a look at some sample code for a simple DrawText detour for Poker Stars:
int (WINAPI * Real_DrawText)(HDC a0, LPCWSTR a1, int a2, LPRECT a3, UINT a4) = DrawTextW;
// Our custom version of DrawText
int WINAPI Mine_DrawText(HDC hdc, LPCWSTR text, int nCount, LPRECT lpRect, UINT uOptions)
{
int rv = Real_DrawText(hdc, text, nCount, lpRect, uOptions);
HWND hWindow = WindowFromDC(hdc);
if (!hWindow)
return rv;
if (IsPokerStarsChatWindow(hWindow))
{
// Text has been written to the Poker Stars chat window.
HWND hPokerTable = ::GetParent(hWindow);
// hPokerTable now stores the HWND of the poker table that
// the chat window belongs to.
}
return rv;
}
And of course, we need to actually install the Detour. The typical time to do this is during the DLL_PROCESS_ATTACH notification.
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)Real_DrawText, Mine_DrawText); // <- magic
DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)Real_DrawText, Mine_DrawText);
DetourTransactionCommit();
break;
}
return TRUE;
}
The other crucial text-drawing API you'll probably want to detour is ExtTextOut (not shown).
How do I tell which window a particular DrawText or TextOut call is drawing to?
As I've said, the text APIs we're detouring get called all over the place. In the case of online poker clients, we're only interested in the text that gets painted in the game chat window. We'd like to ignore all the calls which draw text to other areas of the window.
Here's the basic procedure:
- Every call to DrawText, TextOut, and similar APIs includes a device context (HDC) parameter.
- Figure out the window associated with this HDC by calling the WindowFromDC function, which returns a window handle (HWND).
- Take that window handle and test it to see if it "equals" the window we're interested in. Typically, look at the window's class name and, if necessary, its parent window.
In order to figure out what the window class name is for a particular window, use a tool like Spy++. Here we see that the Poker Stars chat window has a class of "Afx:400000". We also notice that it's a child window of the poker table window.

Putting all that together, we might have some code that looks like this. The following function returns true if the specified window is the Poker Stars chat window.
{
// The Poker Stars chat window is a child of the poker table window, so
// if the window doesn't have a parent, it's not the chat window.
HWND hParent = ::GetParent(hWindow);
if (hParent)
{
// Okay, the window has a parent. Now get the (child) window's class name.
TCHAR className[255];
GetClassName(hWindow, className, 255);
// Compare the window's class name with the PS chat window class name.
// We know from Spy++ that the Poker Stars chat window has a class of
// 'Afx:400000:20'. _tcsicmp returns 0 if they're equal.
return 0 == _tcsicmp(className, _TEXT("Afx:400000:20") );
}
return false;
}
Ultimately, we'd prefer not to hard-code window class names or the parent/child relationships. This information can be passed in from an XML or other settings file, allowing for generic, cross-venue code.
Filtering Out Redundant ExtTextOut and DrawText Calls
As soon as you start to hook the text output APIs you'll notice one thing: TextOut and ExtTextOut get called a lot. Redundantly. Sometimes with empty strings.
In the comments to my last post, a poster by the name of Robert noted:
I have a question for anyone reading... How are you handling the case in which a message is duplicated? Here is an example from a hooked ExtTextOutW to illustrate...
Hooked Function - Table Name = Message
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The flop is [Ts 7s 8h]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: SwimmingPool raises to $10
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: gambleallday folds
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: MGAMW calls $5
MyExtTextOutW-Alto (6 max) - $5/$10 - Limit Hold'em=Dealer: The flop is [Js 5d 6s]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $5
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $10
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: The turn is [3c]
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: Pritt95 bets $10
MyExtTextOutW-Alexandra (6 max) - $5/$10 - Limit Hold'em=Dealer: bordos calls $10
Notice how the turn is "dealt" several times? Also how several bets are duplicated. How can we handle this sort of thing?
Another reader, nickname The Lone Ranger, replied:
Looks like you will need to write a filtering routine. The extent of the filtering will be determined by the frequency and extent of the duplicates. Do the duplicates occur on every hand? Is it always the turn?
I happened to notice both comments shortly after they were posted, so I chimed in.
Yes and one quick and dirty way is to filter by Y value. Provided you don't resize the window or touch the scroll bar, new text tends to enters the window at a constant Y because of the glue effect. Or at least, for any give X,Y associated with a TextOut call, you can answer the question "is this text at the bottom of the window?" The Y may or may not be constant, but it will always be higher than the Y of duped, pushed-upward text.
That's a quick solution to the problem, which is one of DrawText/ExtTextOut spoofing. As new text enters the window, the poker client calls DrawText, ExtTextOut, etc., to draw the text. But it draws that text not only when it's first added to the window, but successively, as new text enters the bottom of the window, forcing the lines above it upward. And depending on exactly how the paint procedure is implemented, the poker client might redraw text at other, seemingly random times.
Another solution would be to get at the actual text stored by the poker client—an approach we'll look at later.
For now, just realize that you'll have to write a little bit of code to answer the question: is the text passed in this particular call to DrawText or TextOut redundant? Here are some ways to do that:
- First of all, on Poker Stars, don't worry about it. We can bypass redundant messages thanks to a quirk in the drawing logic. This technique was demonstrated in the source code accompanying How I Built a Working Online Poker Bot, Part 5: Deciphering Poker Stars and Full Tilt.
- On other venues, you can filter (as noted above) by the Y-value passed to the DrawText or ExtTextOut call.
- You can also store a queue containing the most recent X unique text string you've received via a particular drawing API. When a new call is made, you can check the text against the text you have stored in the list. If it's a dupe, ignore it. Otherwise add it to the queue, and pop the oldest element off. This leverages the fact that it's rare for two identical lines of text to appear in the chat window except in heads-up situations.
- You can also parse each line of text as it comes in and see if it makes sense logically, according to the state of the current game. If you get a line of text "Player A bets 40" followed by "Player B calls 40" followed by "Player A bets 40" you can tell that the last message is redundant, because we never saw the text for Player C's action.
Also keep the following in mind:
- Text output functions call each other! TextOut calls ExtTextOut internally, and DrawText calls DrawTextEx. Focus your hooking on DrawTextEx and ExtTextOut.
- You can mostly ignore calls made with an HDC that correlates to a NULL window. That is, if WindowFromDC returns NULL, you can most likely ignore the call as these calls are associated with a screen DC, and a screen DC is not (usually) the kind of DC that people draw to in these situations. Instead, they'll draw to a window DC associated with the specific window.
- Obviously, you can ignore calls the poker client makes with empty text.
You now have, believe it or not, everything you need to extract any piece of text from any application which draws text using standard APIs.
Conclusion—Parlor Tricks
I'd like to leave you today with one last picture. Look closely.

It looks like a foreign language version of the Internet Explorer "blank" page. This is what happens when we reverse the text passed to our detour, just for fun:
{
if (!text)
return TRUE;
// Make a copy of the supplied string..safely
LPWSTR szTemp = (LPWSTR)LocalAlloc(0, (cbCount+1) * 2);
memcpy(szTemp, text, cbCount*2); // can't use strcpy here
szTemp[cbCount] = L'\0'; // append terminating null
// Reverse it..
wcsrev(szTemp);
// Pass it on to windows...
BOOL rv = Real_ExtTextOut(hdc, X, Y, options, lprc, szTemp, cbCount, lpSpacingValues);
// Cleanup
LocalFree(szTemp);
return TRUE;
}
Not too useful, but interesting, and illustrative. By interposing your code between the target application and the operating system drawing APIs, you can snoop on, modify, hide, replace, or cosmetically tweak the vast majority of text drawn on your system, in any process, any application whatsoever. This is how the images above (at the top of the article) were generated.
I hope I've made the point: extracting text from Poker Stars or any other application whatsoever is not only possible, it's easy. Let's add it to our toolbox and move on.
...emit txen litnU. sliated eht ni s'lived eht...rebmemer dna ,gnidaer rof sknaht ,syawla sA
Posted by James Devlin 208 comment(s)





