How I Built a Working Online Poker Bot, Part 6: Guerilla-Style File Monitoring on Windows with C# and C++

Thursday, July 17, 2008

Introduction

How to write code to detect when any software application on the machine accesses any file on the machine, and how to extract and view any data read from or written to that file in your own application, in real time, using C# and a little bit of C++?

That's the subject of today's article: how to build a file monitor on steroids (or a rough draft of one).

In the above screen shot, we're monitoring the Yahoo Messenger application. And as you can see, it reads and writes quite a bit of data. Public keys, log file snippets, some XML filtering stuff, and so forth. We could have as well chosen to point the tool at Microsoft Word, Grand Theft Auto, or the humble Notepad.exe. But the real power of these techniques is incorporating them into your own applications.

For example, those of you who've been following the online poker botting series can use these exact methods to detect and respond to log file and poker hand history text in real time, as its generated by the poker client. Let's take our tool and point it at the Poker Stars client, POKERSTARS.EXE, by way of example:

Sure enough, we've detected that Poker Stars has opened the log file, and sure enough, we're reading and displaying the log file data as it appears. Which is almost exactly what we'd like our poker bot to do.

But maybe poker's not you're thing. Maybe you're just tired, as I am, of third-rate software treating your hard drive like one of these:

Modern software apps are footloose and fancy-free when it comes to depositing cruft on your machine. I mean the temporary files that never get deleted. The hidden cookies that persist until you reinstall the operating system. And the registry entries which don't get culled when the application that owns them is uninstalled.

Fighting Back

It's a widely-known fact that file I/O on Windows systems is a matter of public record. It's public because, with admin-level access, you yourself can browse to any file on the machine, open the file, edit it, and save it. It's public because you have access to the process address space of every application on the machine (with few exceptions). It's public because applications that read from or write to files do so using a small set of publically available and thoroughly documented functions:

  • CreateFile
  • WriteFile
  • ReadFile
  • CloseHandle
  • And a few others

The above functions are formal members of the exclusive country club known as the Windows API. They're called by millions of applications around the world every day. As you're reading this, various applications on your machine are calling these functions, whether they know it or not and whether you know it or not. In fact, it's hard to do much of anything on a Windows box without calling one of the above functions, either directly or indirectly.

But I'm a .NET programmer! you say. I'm a Java programmer! I don't talk to low-level Windows APIs!

Ah, but you do. You won't call these functions directly, but the .NET framework, the JVM implementation, or whatever library or framework you're using will. So the above set of file-manipulation APIs constitute a sort of bottleneck through which a majority of system file I/O must pass. Now: what if we could somehow get Windows to call one of our functions every time a particular application (any application) calls or causes to be called one of the above APIs?

File monitoring would be a cinch in that case. And that's essentially what we're going to do.

Creating the Injection DLL

The above application was written in C#, but it works in conjunction with a small C++ "workhorse" DLL. Inside this DLL, we'll code custom but equivalent versions of whatever Windows APIs we're interested in redirecting. Here's an example showing our custom version of CreateFile, dubbed "Mine_CreateFile" but you can call it whatever you want:

// Our custom version of the CreateFile Windows API, having the same parameters,
// return type, and calling convention as the version of CreateFile provided by the OS.
HANDLE WINAPI Mine_CreateFile(LPCWSTR lpFileName,DWORD dwDesiredAccess,
                              DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecAttr,
                              DWORD dwCreateDisp, DWORD dwFlagsAttr,HANDLE hTemplate)
{
    // First, call the original CreateFile provided by the operating system.
    HANDLE hFile = Real_CreateFile(lpFileName,dwDesiredAccess,dwShareMode,lpSecAttr,
                                    dwCreateDisp,dwFlagsAttr,hTemplate);

    // Now, do whatever we want with the filename (lpFileName)
    // Now, do whatever we want with the file handle (hFile)

    // Return the same value returned to us by the original API
    return hFile;
}

Pretty straightforward. Whenever this function is called, we call the original CreateFile API through the Real_CreateFile function pointer. Once that's done, we can snoop on the parameters (such as the filename) passed to this function by the target application, report them, etc. Now, in order for this code to work, we have to ensure one thing: our code must be running inside the target application's process.

A New Flavor of DLL Injection

I've written about DLL injection before, and there's a lot of information on this topic scattered around the net and in books. So I'll assume you know that DLL Injection is robust and reliable. I'll assume you know it's a carefully-designed operating system facility, not some backdoor hack. And I'll assume you know (or can figure out) how to inject a DLL. After all, it's a one-liner:

HHOOK myHook = SetWindowsHookEx(WH_CBT, (HOOKPROC) MyHookProcedure, hInstance, 0);

For the purpose of monitoring file I/O in real time, any DLL injection technique will work. But there's one which is particularly well-suited for this kind of thing. I mean the DetourCreateProcessWithDLL function, provided by the Microsoft Detours library:

// Initialize some data structures...
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);

// The following call loads the target executable with our DLL attached
BOOL bSuccess = DetourCreateProcessWithDll( targetExePath, NULL, NULL, NULL, TRUE,
                          CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED, NULL, NULL,
                          &si, &pi, "detoured.dll", "XFileMonitor.Hook.dll", NULL);

// DetourCreateProcessWithDll runs the process in a suspended state. Wake it up.
if (bSuccess)
   ResumeThread(pi.hThread);

The benefit of doing it this way, as opposed to Windows Hooks or other methods of DLL injection? It ensures that our DLL will be present during the target application's startup routine, which is traditionally when programmers open a lot of files. Were we to inject using a Windows Hook, we'd miss application startup every time, because it takes several seconds for a Windows Hook DLL to be propagated across the system.

Detouring the System File APIs

It's not enough to simply create a DLL and inject it into the target application. We have to somehow redirect calls to the system-provided file APIs to the equivalent versions we've created. That's easily accomplished, using Microsoft Detours and a few lines of code.

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        if (::GetModuleHandle(L"XFILEMONITOR.GUI.EXE") == NULL)
        {
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)Real_CreateFile, Mine_CreateFile);
            DetourAttach(&(PVOID&)Real_CloseHandle, Mine_CloseHandle);
            DetourAttach(&(PVOID&)Real_WriteFile, Mine_WriteFile);
            DetourAttach(&(PVOID&)Real_ReadFile, Mine_ReadFile);
            DetourTransactionCommit();
        }
        break;

    case DLL_THREAD_ATTACH: break;
    case DLL_THREAD_DETACH: break;
    case DLL_PROCESS_DETACH:
        if (::GetModuleHandle(L"XFILEMONITOR.GUI.EXE") == NULL)
        {
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourDetach(&(PVOID&)Real_CreateFile, Mine_CreateFile);
            DetourDetach(&(PVOID&)Real_CloseHandle, Mine_CloseHandle);
            DetourDetach(&(PVOID&)Real_WriteFile, Mine_WriteFile);         
            DetourDetach(&(PVOID&)Real_ReadFile, Mine_ReadFile);
            DetourTransactionCommit();
        }
        break;
    }
    return TRUE;
}

C and C++ programmers will recognize this is as a standard DllMain function. The DLL_PROCESS_ATTACH notification tells us that this DLL is being mapped into a new process - in this case, the process of the target application we'd like to spy on. That's as good a place as any to perform the redirection. The following lines of code...

DetourAttach(&(PVOID&)Real_CreateFile, Mine_CreateFile);
DetourAttach(&(PVOID&)Real_CloseHandle, Mine_CloseHandle);
DetourAttach(&(PVOID&)Real_WriteFile, Mine_WriteFile);
DetourAttach(&(PVOID&)Real_ReadFile, Mine_ReadFile);

...cause all calls to the original versions of the function (the ones provided by the operating system) to be redirected to our custom versions of those functions. It happens transparently, behind the scenes, and in memory - you're not actually changing the system DLLs as they exist on disk, so you don't have to worry about corruption. And of course, when the DLL is unloaded from the process (DLL_PROCESS_DETACH) we remove the detours.

Simple.

Spying on the Birth and Death of Files

So you've got your DLL containing custom versions of various Windows APIs. You've injected your DLL into the target process using DetourCreateProcessWithDLL or your injection method of choice. And you've redirected all calls to various system APIs such that your code gets called instead. Now it's time to start tracking files as they're opened and closed by the target application. Let's revisit our CreateFile override, and add a little bit of code to do this:

// This data structure stores the files currently opened by the target app.
map<HANDLE, CString> g_openFiles;

// Critical section guards access to the above collection across threads.
// Elsewhere in code we've called InitializeCriticalSection
CRITICAL_SECTION g_CritSec;

// Our custom version of the CreateFile API.
HANDLE WINAPI Mine_CreateFile(LPCWSTR lpFileName,DWORD dwDesiredAccess,
                              DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecAttr,
                              DWORD dwCreateDisp, DWORD dwFlagsAttr,HANDLE hTemplate)
{
    // First, all the original CreateFile provided by the operating system.
    HANDLE hFile = Real_CreateFile(lpFileName,dwDesiredAccess,dwShareMode,lpSecAttr,
                                    dwCreateDisp,dwFlagsAttr,hTemplate);

    // If that was successful... a new file handle has been born
    if (lpFileName && hFile)
    {
        CString sFileName = lpFileName;
        if (!sFileName.IsEmpty())
        {
            // Store it! Multiple threads may call this function at the
            // same time, we we'll use a critical section to ensure that
            // only one of them manipulates g_openFiles at a given time.

            ::EnterCriticalSection(&g_CritSec);
            g_openFiles.insert(pair<HANDLE, CString>(hFile, sFileName));
            ::LeaveCriticalSection(&g_CritSec);
        }
    }
    return hFile;
}

Every time the target application calls (or indirectly causes to be called) CreateFile, we're storing the filename and the HANDLE associated with it. You could also take this opportunity to dump some information in a log file or (in the case of the XFileMonitor) display a notification in your GUI (which is running in a different process) that a particular file has been opened.

Snooping on File Reads and Writes

Similar to the custom version of CreateFile above, we'll create custom versions of the WriteFile and ReadFile APIs. The target application will call our versions of these APIs whenever it tries to read data from or write data to the disk. Let's take a look at our version of ReadFile.

BOOL WINAPI Mine_ReadFile(HANDLE hFile,LPVOID lpBuffer,DWORD nNumberOfBytesToRead,
                            LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
{
    // Call the original version of ReadFile provided by the OS
    BOOL bSuccess = Real_ReadFile(hFile, lpBuffer, nNumberOfBytesToRead,
                                  lpNumberOfBytesRead, lpOverlapped);

    // See if the handle being written to is one we know about
    map<HANDLE, CString>::const_iterator iter = g_openFiles.find(hFile);
    if (iter != g_openFiles.end())
    {
       // Yes, we collected this handle in an earlier call to CreateFile.
       // Retrieve the file name and do something with it.
       CString fileName = (*iter).second;
    
       // TODO: lpBuffer now contains whatever data the application read. This
       // might be text data (either single-byte or Unicode) or it might be
       // binary.. we're free to examine and/or transmit it as we see fit
    }
    return bSuccess;
}

First we pass the call along to the original API. Then we take the supplied HANDLE and see if it's in the list of handles we created by monitoring calls to CreateFile. If it is, we can easily get the associated filename. If it's not, we can still get the filename, but it's a bit more work, and left as a reader exercise.

Identifying Binary vs. Text Data the Quick and Dirty Way

If you're targeting or trying to monitor a particular file or application, such as monitoring the log file created by the Poker Stars gaming client, you already presumably know about the files you want to snoop on. You know for example, that hand history files are text files. So when you detect a write to a hand history file, you can be sure it's text data that's being written.

But what about when you don't know anything about the files? What if you're trying to write a generic file monitor, such as the one shown above?

The problem is that ReadFile and WriteFile treat everything as a buffer of bytes. It might be text data, that's being written. Then again, it might be binary data. You don't really know, and there's no quick and easy way to tell the difference, because binary data and text data will frequently use some of the same underlying values. For example, if the first byte of data in the buffer has the value 65, it might be the letter "A" (which, in its single-byte form, has the value 65) or it might be that you're reading a binary file, and the 65 indicates something (anything) else entirely. Maybe it means there are 65 records in this particular file, or maybe it means that the average price of a pound of bok choi in Moscow is 65 pennies. You don't know, I don't know. Only the person who wrote the code knows.

So what to do in that case?

Well, we can get a quick-and-dirty approximation of whether a given buffer of data is text or binary by:

  • Checking the file extension. This isn't always reliable, since you can give any file, whether text or binary, any extension. But it's usually reliable.
  • Checking the data being written to see if it falls within normal ranges for text character data. Again, not always reliable, but often reliable.

You already know how to get the file name (by detouring CreateFile). So let's look at some quick-and-dirty code to scan a particular buffer and see if the data in that buffer can successfully be interpreted as ASCII text. Specifically, let's check each byte in the buffer to see if it falls within normal ranges for ASCII (single-byte) character data. Here's a sample function which does that:

// Check the first N bytes of any arbitrary data stream to see
// if they fall within normal printable ranges for ASCII/MBCS
// character data.
bool IsAsciiText(LPCSTR buffer, int testLength)
{
    int validChars = 0;
    for (int index = 0; index < testLength; index++)
    {
        char c = buffer[index];

        // if the value of C is negative, or if it
        // identifies a non-printable ASCII character,
        // then this is probably binary data.

        if (char < 0 || !(isprint(c) || isspace(c)))
            return false;
    }

    // Every character we tested was within the range
    // for printable ASCII characters. This is probably
    // text data.

    return true;
}

Now, this little function is far from complete. It only handles ASCII (single-byte) character text. It doesn't correctly handle embedded NULLs and so forth. But it's good enough for demonstration purposes and pretty easy to enhance with additional, smarter checks.

Transmitting File Read and Write Data to Managed Applications

So you've got your ReadFile and WriteFile detours in place and working. You're able to roughly detect when particular data is text or binary, by examining the file name as well as the data stream itself. Now how do you go about transmitting all this data back to your application, running in a separate process?

Obviously we'll have to use some sort of inter-process communication. The exact flavor is up to you, but the above XFileMonitor tool uses WM_COPYDATA because it's simple and straightforward.

// A little structure to communicate relevant file read/write contextual info
struct FILECONTEXT
{
   HANDLE File;       // the handle of the file read/written
   int OriginalAPI;   // a number uniquely identifying the specific API
                      // (such as ReadFile, etc) that was called.
};

// Transmit file read/write data back to the XFileMonitor (or any) application..
void Transmit(Win32API sourceAPI, HANDLE hFile, LPCWSTR text)
{
    // We're using WM_COPYDATA, which is a window message, so we need a target window.
    HWND hWnd = ::FindWindow(NULL, L"Coding the Wheel - XFileMonitor v1.0");
    if (!hWnd)
        return;

    // Set up the COPYDATASTRUCT expected by Windows.. the length should be the
    // size of our (custom) FILECONTEXTstructure, plus the length of the file
    // data we're sending, expressed in bytes, with enough room for a terminating
    // NULL.

    COPYDATASTRUCT cds;
    ::ZeroMemory(&cds, sizeof(COPYDATASTRUCT));
    cds.dwData = action;
    cds.cbData = sizeof(FILECONTEXT) + ((wcslen(text)+1) * 2);

    // Allocate the outgoing array
    LPBYTE pOutData = new BYTE[cds.cbData];

    // Place a FILECONTEXT structure at the front of this array
    FILECONTEXT ht;
    ht.File = hFile;
    ht.OriginalAPI = sourceAPI;
    memcpy(pOutData, &ht, sizeof(FILECONTEXT));

    // Place the text immediately following the structure..assumes any
    // single-byte text has already been converted to Unicode
    wcscpy((LPWSTR)(pOutData + sizeof(FILECONTEXT)), text);

    // Send it off
    cds.lpData = pOutData;
    ::SendMessage(hWnd, WM_COPYDATA, (WPARAM)::GetDesktopWindow(), (LPARAM)&cds);
    delete [] pOutData;
}

Then in our managed code application .EXE, we can use some simple P/Invoke to access the data. First, let's override OnWndMessage:

// WM_COPYDATA message ID
const int WM_COPYDATA = 0x4A;

// Get access to the WM_COPYDATA message
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_COPYDATA)
        OnCopyData(ref m);

    base.WndProc(ref m);
}

Now let's define the OnCopyData function. In order to do that, we'll need to create P/Invoke compatible versions of the COPYDATASTRUCT (defined by windows) and FILECONTEXT (defined by us) structures.

// Managed version of Win32 COPYDATASTRUCT (defined by OS)
private struct COPYDATASTRUCT
{
    public int dwData;
    public int cbData;
    public IntPtr lpData;
};

// Managed version of FILECONTEXT struct (defined by us)
private struct FILECONTEXT
{
    public IntPtr Handle;
    public System.Int32 OriginalAPI;
}

// Decode WM_COPYDATA sent from the unmanaged C++ file monitoring DLL
private void OnCopyData(ref Message m)
{
    COPYDATASTRUCT cds = new COPYDATASTRUCT();
    cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));

    FILECONTEXTht = (FILECONTEXT)Marshal.PtrToStructure(cds.lpData, typeof(FILECONTEXT));

    string strBufferData;
    int unManagedSize = Marshal.SizeOf(typeof(FILECONTEXT));
    unsafe
    {
        byte* pString = (byte*)cds.lpData.ToPointer();
        pString += unManagedSize;
        IntPtr pManString = new IntPtr((void*)pString);
        strBufferData = Marshal.PtrToStringUni(pManString);

        // TODO: strBufferData contains the text sent over by the
        // file monitor. Display it, analyze it, whatever.
    }
}

 

And that's all there is to it.

I had very little time to throw this code together so I hope you'll excuse some of its obvious warts such as the use of unsafe C# code. Think of it as a quick and dirty example of techniques rather than production botting code. This code will be extended in Part 8 of this series (already written, and due shortly) in a direction you might not have guessed...

Putting It All Together: the XFileMonitor Application

You've seen what it looks like. You've read about the techniques behind it. Time to download the thing and test it out.

The source code contains four projects:

  • An EXE project, implemented in C#
  • A DLL project, implemented in C++
  • 2 Detours projects: detours.dll and detoured.dll.

These projects boil down to essentially two files, which contain all the source code demonstrated above. In addition, there are two projects needed by the Detours Library which you can mostly ignore. Boost is not required for this project, and neither is NMAKE. So those of you that had trouble building the FoldBot will hopefully find this project is a cleaner build.

And as usual, my standard source-code disclaimer applies!

Good luck, and enjoy.

Tags: file monitoring, poker bot, Microsoft Detours, file I/O, online poker, poker

62 comment(s)

Very impressive article.

Thank you for eliminating the need for NMAKE. It was a nightmare on my system.

AWESOME

So my question is do you think PS is already blocking your app with this file name and banning accounts that have been seen using it? Its easy enough for them to scan and see what programs are running and then close someones account because of it. I have seen programs that change there exe name each time they run but I have wondered if that really will work since they could be tracking what the appication is doing and what it monitors. A bit of a ramble but let me know your thoughts.

Poker Stars is so behind the times on this I think if they were to do that, they'd end up teaching a whole new generation of botters how to stealth their applications. Maybe they'd prefer not to make a big stink about it.

Just my .02 great post btw..

And I'm guessing this is one of many reasons security experts say 'there's no security on the client' because of techniques like this and actually... your entire series makes me question what applications are really "made of".. in physics we learn that matter is mostly empty space and after reading your site I'm starting to realize that software is basically the same. Not at all what you think it is on the surface.

The single most impressive explanation of this topic I've ever read, by the way. There are tools you can buy to do this kind of thing but I've never seen a detailed explanation of how to do it in code. Also nice to see some C# in the mix this time.

"Its easy enough for them to scan and see what programs are running"

Using Windows APIs? Too bad there's no way to hook them and control what they return ;)

Leading to a situation in which they try to detect you, you try to detect them, you're both now injecting code into each other's process space and spawning cloven-hoofed injector demon rootkits and next thing you know, BOOM.

The Universe explodes.

Lets all help out here with this series and do some translation from C++ to C# for the gui part. If you look at XPokerBot you will see that all the code is there and can be accessed from C# the same way that it is done in XMonitorFile just need to convert it to C# from C++ I am going to start and will post what I do but I will say this C++ is not my stronge suite.

Would you advise using the same technique that you showed for pokertime to actually click the buttons for PS and FTP?

"Lets all help out here with this series and do some translation from C++ to C# for the gui part."

I'm working on this too, just wondering if maybe the author already has a C# version...?

If anybody else has done this yet speak up! :-)

WOW. Amazing post, and tool. Quite eye-opening to see what files a particular program touches. I've been pointing this thing at every EXE on my system having quite a bit of fun.

Does that qualify me for geek status? ;-)

I'm really enjoying these articles, keep it up! Poker bot making is a fun hobby.

[url]http://www.icmbot.com[/url]

Ok lets start with translating the types of items in the struct's from C++ to C# So far here is what I have and here are the problems that I have. Work Correcyly C++ C# INT System.Int32 BOOL System.Boolean double System.Decimal

Not sure of double Limits[2]; HWND Window; TCHAR TableName[64];

I will post if i find answers.

Ok right after posting that I found this seems to work well for the struct conversions http://tangiblesoftwaresolutions.com/Demo.htm

Although when you convert the struct it turns it into a class which C# does not like So trying to just do struct to struct when I have public string TableName = new string(new char[64]); I get the error cannot have instance field initializers in structs

Man, you really crazy coder!!! Thank you for your impressive series of articles.

For the C# part i made some entries on part 5 of this series... got it up and running now with an entire C# frontend... just fast mock-up. Relying on the source from the monitor client (from part 5) to send the data to my C# application, and then letting that application handle the interpretation of the chat (btw only doing full tilt poker venue, where the hole cards is displayed in the game chat).

@anynomous (two posts before this one) i think i solved that problem (char[64]) in the code part i displayed in part 5 of this series.

Hello all I just built the MonitorBot and the file monitor thingy. I'm seeing some of the poker clients open up some strange files. Stuff in my browser history and so forth, registry entries, and etc. Anybody else seeing the clients opening files they shouldn't be?

AS mentioned in one of the first parts of this series, the poker software can in some regards be looked upon as spyware, i think thats prbably the best explanation.. try look at that post (think its post 1 or 2)

No i don't see it oepning any strange files. Just look through the code. YOu should be able to see if there is anything in there that it is accessing that you don't want it to.

Hi LastChance, I mean the poker software (Poker Stars) is opening strange files, not the tool. For example I see it (Pokerstars) look at my IE history folder and some other stuff... am researching.

Very intersting keep us posted. What are you using to track what its opening? Its probably looking for bot programs running. Have you tried seeing what FTP is looking at?

PStars has a list of prohibited software that players aren't supposed to be using while they play. Stuff like bots and statistics programs that use centralized databases and stuff like that. To enforce "prohibition" they snoop on everyones PCs looking through things like the registry, start-menu, program files, IE history, etc. Apparently running a bot or stat collector is a bad thing but rifling your PC (and mine) is perfectly OK.

OH BOY:) anyone running on 64bit Vista?

I am getting this when i try to call InstallHook on any program.

An attempt was made to load a program with an incorrect format.

Fixed that by changing the build properties to x86

But now I no longer get any messages cominb back from C++ Anyone doing this in 64bit?

It works on 64 bit just have to change it to build as X86 and it will work fine.

The latest version of the Easyhook library can be found here. The one at google is an old version

http://www.codeproject.com/KB/dotnet/EasyHook64.aspx

How do we prevent poker sites from detecting our injected dll when they can simply run an application like this to detect them? http://www.edmond-hakmeh.com/hack%20tools/utilities%20and%20hacking%20tools/utils/injected_dll.html

In a past life I was loosely associated with a poker stat collecting software app. A good product but I'm not here to advertise for anyone. From extensive readings in that forum and others I can tell you that only a few poker-sites state adamantly that bots and stat collection software that used centralized databases are not allowed. Of those... only two (PS & PP) actually use their client app to check your system. They don't disallow all DLL injections on your system... too many of those are required by the system or major apps or for good purposes. They check for specific known products by name or by tell-tell signs of it on your system (registry, file system, IE history). Products that have sold thousands of copies. Now PS is stating that ICM calculators are disallowed as well... so those guys will now have to watch out. The two poker-sites y aren't worried about the little guy building a bot for free that could be under any name today and maybe a different name next week... that may or may not be able to get his bot working, and may or may not be able to break even with it when he does. They're only worried about big bot software companies whose technology could be used in collusion rings, or centralized stat databases where paying members get tons of stat data on players they've never played against or even seen. I know a lot about this... but I'm still here enjoying this fun project. James got me excited about programming again. Keep em coming James.

How do I determine for what table the hole cards in the log file belong? Any ideas?

Okay I am struggeling with this. I found that windows get assigned ID's so I can use that to track which window was assigned the hole cards. Not the next problem arises, the log file gets mangled when multitabling. The lines written from the different games overlap eachother.

Simon: each line in the log file should be prefaced with the HWND of the window to which it belongs. Take the HWND and use that to find out which table the action occurred on. Have you tried this approach?

Anyone have any idea how I can get the hand that is dealt to me using the William Hill poker software?

It writes to a log file, but only after the hand is finished. The hand isn't displayed in the chat window (even with all dealer messages being shown), I can't see how I can get my hand in real time!?

Any ideas appreciated! :)

P.S Got my "bot" working on PokerStars, but would like to get it working for William Hill Poker!

I haven't looked at William Hill but you have (at least) two options:

1) Use a simple pixel test to read the hole cards. I think there was a diagram of this way back in Part 1.

2) Perform some basic disassembly to figure out where in memory the hand values are being stored.

Great stuff, thanks James, especially liked your section on "Identifying Binary vs. Text Data the Quick and Dirty Way"

How can I preface log messages with the HWND of the window to which it belongs? (multitabling)

Any piece appreciated!

@Walli

You are probably already keeping a list of poker windows and their corresponding table names (if not, you should). When you get a log message just see if it contains the table name.

Wow; this is the most advanced poker bot website I came across; now to be honest, very very few people can implement such a profitable bot; but if you can, you are the man

Hello everybody. Im only 16, im from slovakia, im interested in programming a bot, but this is too difficult for me, couldnt somebody write for me please only a simple fukntion, or part of a programme, that will return only the cards (means my hand, flop, turn, river)? this is the death point of my work, cuould somebody help me? i need only the funtion for reading cards and save it to the variables. Thank you very much. Igor jebectt-gmail-com

*optionaly (im writing) in C++ BUILDER

*sry for another comment, but i forgot that i need only for pokerstars.THX.Igor

Pokerstars chat doesn't seem to be working any more, as in it's not capturing the chat :(.

I checked the log file, and they only write some obscure reference to board cards now:

DealBoardCard 0 DealBoardCard 1 [2009/04/24 17:44:02] DealBoardCard 2 [2009/04/24 17:44:04]

:(

AWESOME BLOG! im not really into building a real bot but this in a great introduction to windows API programmming (i come from Unix)

im trying to figure out all the possible DLL Injection techniques, but im puzzled by that comment on line #314,315 of XFileMonitor.Hook.cpp:

[i]// Install the global window procedure hook, causing this DLL to be mapped // into the address space of every process on the machine. (...)[/i]

I thought Detour would map the DLL only in the target application and not into the address space of EVERY PROCESS on the machine..?

thanks for enlightening me!! and keep up the AWESOME work! hehe

luis

Awesome and really impressive

Does it still working??? It seems to me that it is not. I tested a similar solution with Party Poker but it does not present any log until the end of the round.

Anyone experiencing the same problem??

I'm not a programmer, but some day would like to be. I just want to know what is going on with machine. I want to learn to spy on the spies. When I started this project I went and downloaded microsoft visual C++ 2008 Express Edition.

Unfortunately, neither MFC nor ATL are supported in the Visual C++ 2008 Express Edition. The header files and the related libraries and DLLs are only licensed with the Visual Studio 2008 Standard and additional commercial editions. There is no download source available. Does any one else have this problem and can you help me find alternative libraries for the few cases of ATL that you seem to be depending on.

<atlstr.h> in particular, or maybe even recommend a different compiler.

Thanks in advance

You lead this donkey to the water and I'm damn sure going to drink it.

Hi everyone,

I would like to congratulate James Devlin because your web site is really amazing. From the technical point of view, it is 7 stars, not 5. It is outstanding.

Right now, I would like to find someone to cooperate with me on a poker software project whose goal is to improve SNG table selection (Pokerstars). As James Devlin states, table selection is very, very important and I need to improve that area. What I am looking for is someone that is able to develop a software that retrieves the content of any listbox on Pokerstars. I have already developed all the software needed to evaluate if we should or should not register on each specific tournament taking into account some statistic criteria ....

If you are interested, please post your email on a comment and I will contact you afterward.

Hey Poker of Aces, I work on another Poker project. I focus on cash games. But maybe we could exchange some knowledge and how to investigate new Poker rooms.

Feel free to write me an email: Klabautermann176@web.de

I tried running this and the fold bot on Vista.

After selecting the pokerStars.exe I get an error saying that the GUI has stopped working.

Any ideas anyone?

Leading to a situation in which they try to detect which seek to detect, both are now inject code into every process space and spawning an injector rootkits hoofed demon and next thing you know, BOOM. Thank you for taking the time to write this blog post. Much appreciated, very valuable information.

We will help everyone here with this series and make the translation of C + + to C # for the graphical user interface. If you look at XPokerBot see that all code is there and can be accessed from C # in the same way as is done in XMonitorFile only have to convert to C # in C + + that I will start and will post what I do but I will say this in C + + is not my stronge suite. There are other measures of self-respect for a man, than the number of clean shirts he puts on every day.

Do you recommend using the same technique that was shown PokerTime actually click on the buttons to PS and FTP? I go to your website every once in a spell and I fair eff to say that I like your model!

"We will help everyone here with this series and some of translation of C + + to C # for the graphical user interface. really good quote

I did it using Python... it is nice to see that we can use C#, that is my favorite language. best Los Angeles doctors

Hello, I have a problem running the XFileMonitor program. I double click on the exe, the GUI shows up but when I choose an exe file for monitoring I get an exception:

*** Exception Text *** System.DllNotFoundException: Unable to load DLL 'XFileMonitor.Hook.dll': Impossibile avviare l'applicazione specificata. La configurazione dell'applicazione non รจ corretta. Una nuova installazione dell'applicazione potrebbe risolvere il problema. (Exception from HRESULT: 0x800736B1) at XFileMonitor.Gui.Form1.InstallHook(String targetApplication) at XFileMonitor.Gui.Form1.btnLaunch_Click(Object sender, EventArgs e) at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

etc..

The file XFileMonitor.Hook.dll is in the same directory of the main exe, do I have to put it somewhere else? Thanks in advance, great articles :)

Mimmo

@Mimmo

set your XFileMonitor.Hook.dll path in environment variable...n check path in cmd by typing "PATH"

this is what i have done when i got same problem..now its working fine.

@Mimmo @mj

Still not working (same error as mimmo). Did you get it working ? PATH=C:\XFileMonitor\bin

Thanks.

Hello everyone, I'm trying to run the XFileMonitor in a WPF .NET 3.5 project on windows 7 64. After i browse for an application to monitor, it works for like three seconds, i receive two "files opened" messages, and one "file writes and reads" message, then the app i chose to monitor stops working and a windows pops up asking me to chose between checking online, closing the program and debugging the program. Can anyone help me please?

Hi all. I noticed that many poker rooms now show not the suit letter, but suit icon. Who can advice me the way to work with it?

Use the form below to leave a comment.






Coding the Wheel has appeared on the New York Time's Freakonomics blog, Jeff Atwood's Coding Horror, and the front page of Reddit, Slashdot, Digg.

On Twitter

Thanks for reading!

If you enjoyed this post, consider subscribing to Coding the Wheel by RSS or email. You can also follow us on Twitter and Facebook. And even if you didn't enjoy this post, better subscribe anyway. Keep an eye on us.

Question? Ask us.

About

Poker

Coding the Wheel =
Code, poker, technology, games, design, geekery.


Hire

You've read our technical articles, you've tolerated our rants and raves. Now you can hire us anytime, day or night, for any project large or small.

Learn more

We Like

Speculation, by Edmund Jorgensen.