In How I Built a Working Online Poker Bot, Part 7: Extracting Text from 3rd-Party Applications, we talked about how to write code to snoop on the text painted to an application's user interface.
We noted that most applications, when they want to draw text, do so by calling one of a handful of standard operating system APIs. And we realized that we could "detour" these APIs in memory, such that whenever an application tried to call these APIs, it would end up calling our code instead. Our code would then have a chance to analyze the text being painted.
Several months later, one of the companies mentioned in the above-linked post—PokerStars—updated their software.
Specifically, they started painting text using a proprietary solution that doesn't rely on standard operating system APIs. Our technique stopped working, and tools which depended on it stopped working. In PokerStars Slams Hammer on Third Party Software Industry, we can see an email PokerStars sent to tool developers.
I regret to be the bearer of bad tidings, but this was the purpose and intent of the change -- to deny malicious third party developers access to hooking 'DrawText' to extract data from the dealer chat window. We have completely avoided the use of 'DrawText' and 'ExtTextOut' in favor of a proprietary internal solution, and as such the contents of the chat cannot be extracted any longer.
While we recognize that there are many third party odds calculators and other real time tools that are absolutely not malicious and in fact are permitted on PokerStars, malicious tools such as bots and dataminers also retrieve their data in the same manner, and it is against those developers that such measures are designed.
While unfortunate, some permitted apps are likely to be hobbled by the changes intended to prevent cheating, and there's little that we can offer you in way of a solution or a way around this. We can't offer you an API to the data (or malicious developers would hack it), and we can't go back to using DrawText. We believe it more important to keep the malicious programs out than it is to allow permitted programs to continue to function.
I wish I had a solution for you, but the truth is that additional changes will be forthcoming to make it even more difficult to gather "real time data" from the client as well. If a cheating program manages to find a way to code around these changes, we'll plug whatever hole they exploit as well (which may well break any solution you come up with for your applications as well).
I wish I had better news for you, but that is PokerStars' position on the gathering of real time data from the client. All I can offer is apologies that your application had to be caught up as "collateral damage" in the war on cheating applications.
Just to be clear, the issue here is that online poker tool developers have been programatically extracting text from the PokerStars game chat window.

This window gives crucial information about the betting actions in each game. The text contained in this window is not private in any way—it's intended for human consumption—but PokerStars would prefer that third-party software tools not have access to it. In their view, that capability makes it too easy for malicious software to hook into the PokerStars client. So they've taken steps to prevent it.
But is the PokerStars game chat text truly gone, such that it can never be extracted? Or is it merely hiding?
In order to answer that question, let's download a powerful but free piece of software called WinDbg, the core member of the Debugging Tools for Windows package. Here it is, in all its glory:

Most programmers who were weaned on a diet of Visual Studio development rarely, if ever, use any debugger other than the built-in Visual Studio debugger. That's usually okay because the Visual Studio debugger is integrated, eloquent, easy to use, and quite powerful. But there are many debugging facilities which aren't exposed through the Visual Studio debugger UI.
Once you've installed WinDbg, you should really take the time to configure your symbol files properly. But this isn't strictly necessary for today's work.
Let's choose the "Open Executable" option from the File menu:

Browse to the PokerStars executable:

A WinDbg command window will open. We'll see messages for each of the DLLs loaded by the target application, then a "break instruction" followed by the context (the state of the CPU registers) of the primary thread.

By default, when we launch an application through WinDbg, it opens the application in a state of suspended animation. That is, it invokes the application and then immediately halts it. When the target application is in this state, you can't interact with it in any way (except, of course, through the debugger).
So let's type g into the WinDbg command prompt (or use the toolbar button) in order to wake PokerStars up and give it a chance to run. The PokerStars main window should appear, and we'll see some more messages stream into the WinDbg command window.

Then we'll open one or more PokerStars tables, and let them run for a couple minutes, in order to accumulate some text in the game chat window.

After that, it's time to "break into the target" as we say. Hit the WinDbg Break button on the toolbar. The PokerStars application will freeze.

Now, I should mention that every game-related message in the PokerStars chat window is prefaced with the following phrase:
Dealer:
For the purposes of this experiment, we'll use that as a quick and dirty way of locating these game chat messages in memory. In other words, we want to scan the entire PokerStars process—all of it—for the above phrase.
In order to that, we'll execute the following command:
s -u 0x00000000 L?0xffffffff "Dealer: "
Loosely translated, this says: search, starting at memory location 0x00000000, and ending at memory location 0xFFFFFFFF, for all instances of the Unicode text phrase "Dealer: " in the PokerStars process. (We can tell WinDbg to search for ASCII text by replacing the "-u" with "-a".)
Type that into the WinDbg command window and voila!

As you can see, there are numerous instances of this text in PokerStars process memory—one for each line of text in the game chat window, in fact, plus a few occasional duplicates. For those of you who are new to "hex editor UI," let's look at it with some helpful annotations:

Each line corresponds to a specific memory address whose bytes, when interpreted as Unicode text, spell out our catchphrase. Clearly, the PokerStars chat text still exists, the same as it ever did. But let's take a closer look. Open a Memory window from the menu or by hitting Alt+5:

This will let us examine the actual bits and bytes of process memory.

At the top of the window, in the field labeled "Virtual," we'll plug in the address of one of the "Dealer: " text strings shown above. (The strings inside the red box. Don't use the actual values shown above; use the values returned by your copy of WinDbg).

We are now staring at the bits and bytes of a single line of text from the PokerStars game chat window. So when PokerStars says that game text "can no longer be extracted" we should take that statement, as always, with a grain of salt.
Extra Credit: Watchpoints
Most programmers are familiar with the concept of a breakpoint. When we're debugging an application, and execution reaches a breakpoint, the debugger breaks into the application and gives us a chance to start single-stepping through it, examining variables and so forth.
Most programmers eventually learn that we can set conditional breakpoints. Instead of halting the application when a particular breakpoint is reached, conditional breakpoints halt the application when a particular condition is true. For example, "break if the value of foo is 93".
But relatively few programmers are aware of data breakpoints, also known as watchpoints. These tell the debugger to halt the application not when a particular piece of code is executed, but when a certain memory address is accessed. In any way whatsoever.
For example, if we happen to know that a certain piece of text is sitting in process memory at a certain address, and we want the debugger to tell us whenever that piece of text is passed into a function, copied, or deallocated, we can create a watchpoint.
Within WinDebug, the ba command ("break on access") is used to set up a watchpoint. For example:
ba r4 0x04EFF210
This command reads break the application whenever the 4-bytes of memory starting at address 0x04EFF210 are accessed. Sounds good, except that breakpoints are usually set in the context of a specific thread, and PokerStars, like most applications nowadays, has multiple threads:

So what we want to do is somehow set the data breakpoint across all threads. Lo and behold, WinDbg allows us to do that by using a special prefix:
~* ba r4 0x04EFF210
The ~* instructs WinDebug to set the breakpoint across all (*) threads. We could also use it to set the breakpoint for specific threads.
Let's go ahead and type the above command into the WinDbg command line, replacing the address 0x04EFF210 with one of the addresses returned when you executed the s command above.
Once we've done that, type g to resume the application.
If we've timed things correctly, the breakpoint should be hit almost immediately. We'll continue typing g to resume the application each time the breakpoint is hit and we'll end up with output similar to the following:

At this point, we've learned two things:
- Where text exists inside the PokerStars (or other application) process.
- The state of the process, including all threads, stack frames, and registers, each time this text is accessed.
We know what the text is, and with some careful research, we can figure out what functions are accessing this text. In order to do that, we can examine the call stack each time the debugger halts the application: "Breakpoint 0 hit."
We can then work backwards, and start to determine which functions are responsible for creating, reading, and copying PokerStars game chat messages, as well as the structure of those messages in process memory. Without ever knowing the name of any of these functions or structures.
Once those functions are identified, we can start to interposition custom code in order to redirect execution flow from the original versions of those functions to custom code, written by us...giving us a chance, once again, to read PokerStars game text in real time.
That's just one way of doing it. There are others.
And of course, PokerStars is just an example. The above technique will work with any Windows application, although, as always, some tweaking is required. Stay tuned and we'll discuss some of the details and, after a three month hiatus, take a look at some more source code.
Happy disassembling.
Posted by James Devlin 49 comment(s)





