Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

FreeCell & Hearts, Behind the scenes

0.00/5 (No votes)
28 Jan 2003 1  
This article shows the insides of the freecell and hearts games, it uses the a library of reading and writing another process memory.

Sample Image - freecellreader.jpg

Sample Image - freecellreader1.jpg

Introduction

Yep, again. For those of you who liked my first article "Minesweeper, Behind the scenes" I present here his natural sequel. The idea came when a friend of mine read the former article and joked me about it, "now do hearts.." he said, so I did and this article is the final result. At this point I would like to thank some friends who helped me, Itay Langer, Michael Kuperstein and Yoav Sion (in alphabetical order).

Two weeks ago, when I wrote the first article, I've explained the use of API functions with C# (using P/Invoke) to read another process memory. Also I've written an example for using this class library in order to read the Minesweeper mines map. In that article I've claimed and I still claim that the goal of the article was not how to hack Minesweeper. The goal of that article was to provide the reader a simple class library in C# that will allow them to read another process memory. To my surprise, many people who read the article was very interested in the example I've brought. Because of that I cant really claim that the goal of this article is to learn how to use some new API functions in order to have functionality unsupported by C#. The goal of this article is to see what is happening behind the scenes of the two Microsoft card games: FreeCell and Hearts. In spite of this goal my conscience cannot let me publish an article with no new coding feature, So I've enhanced the library and now it supports also writing into another process memory. I've even done a use of this function in one of the examples.

Main Goals

  1. FreeCell behind the scenes and developing a cool app that uses this knowledge.
  2. Hearts behind the scenes and developing a very cool app that uses this knowledge.
  3. Write to another process memory using the WriteProcessMemory API function.

Note: The first two sections are using a debugger to investigate the games, my personal favorite debugger is Olly Debugger v1.08 but any debugger will do.

Section 1: FreeCell, How does it work?

I imagine you ask yourself what is to crack in FreeCell? you already see all the cards! Well the point is to understand what it does and how it gets these cards. Also suppose you can read the cards, you can write a program that plays Freecell by herself.. but you must have a way to read the freecell memory.

The first step, like always, is opening the file in the debugger. In this case we need the freecell.exe file which is found in the windows\system32 folder. After opening the file we need to find an interesting starting point. When you look at the beginning of the file you see a list of functions imported from dll's as follows:

01001188 DD USER32.GetDlgItemInt
0100118C DD USER32.SetWindowTextW
01001190 DD USER32.wsprintfW
01001194 DD USER32.GetSysColor
01001198 DD USER32.GetWindowDC
0100119C DD USER32.IsIconic
010011A0 DD 00000000
010011A4 DD msvcrt._except_handler3
010011A8 DD msvcrt._controlfp
010011AC DD msvcrt.__set_app_type
010011B0 DD msvcrt.__p__fmode
010011B4 DD msvcrt.__p__commode
010011B8 DD OFFSET msvcrt._adjust_fdiv
010011BC DD msvcrt.__setusermatherr
010011C0 DD msvcrt._initterm
010011C4 DD msvcrt.__getmainargs
010011C8 DD OFFSET msvcrt._acmdln
010011CC DD msvcrt.exit 
010011D0 DD msvcrt._cexit 
010011D4 DD msvcrt._XcptFilter
010011D8 DD msvcrt._exit
010011DC DD msvcrt._c_exit
010011E0 DD msvcrt.isdigit
010011E4 DD msvcrt.time
010011E8 DD msvcrt.srand
010011EC DD msvcrt.rand

This is only the final part of the list, but If you look carefully you see that the last imported function listed is the function rand from the msvcrt dll, which is off course the randomize function from the microsoft visual c runtime dll. So I've decided to search where this function is being used.

I've found two places where this function is being used. Then I put a breakpoint on both places and run the program. After I've started a new game the program stopped on the first breakpoint once and 52 times on the second breakpoint. Here is the part where the brain needs to kick in, mine said that the first breakpoint randomize the game number and the second breakpoint randomize the position of each card (or the card of each position) (remember you got 52 cards..).

So, I did a little test to prove me theory. I ran the program till the first breakpoint, then step over the rand function and look at the eax register, the value of this register was 0x00006A26, then I continue to run the program, the game number (displayed in the caption) was 25254.. bingo, 0x6A26 = 25254. So now I knew I was right I needed to find where freecell stores this value and the cards values, so I could read them later with my program. I started a new game and now I've investigated the second breakpoint. A few instructions before the breakpoint I've found a very interesting piss of code. The code I've found store a value in a specific place in the memory, and then formatted the string "FreeCell Game #%d" with this same number, so THIS is where it stores the game number.. The code is presented here:

010031D0  MOV DWORD PTR DS:[100834C],EAX
010031D5  PUSH ESI
010031D6  PUSH EDI
010031D7  PUSH 80                                  ; /Count = 80 (128.)

010031DC  MOV ESI,freecell.01007880                ; |

010031E1  PUSH ESI            ; |Buffer => freecell.01007880

010031E2  PUSH 12F            ; |RsrcID = STRING "FreeCell Game #%d"

010031E7  PUSH DWORD PTR DS:[1007860]              ; |hInst = 01000000

010031ED  CALL DWORD PTR DS:[<&USER32.LoadStringW>>; \LoadStringW

010031F3  PUSH DWORD PTR DS:[100834C]
010031F9  PUSH ESI                                 ; |Format => ""

010031FA  MOV ESI,freecell.01007820   ; |UNICODE "Cards Left: %u"

010031FF  PUSH ESI                                 ; |s => freecell.01007820

01003200  CALL DWORD PTR DS:[<&USER32.wsprintfW>]  ; \wsprintfW

01003206  ADD ESP,0C
01003209  PUSH ESI                                 ; /Text

0100320A  PUSH DWORD PTR SS:[EBP+78]               ; |hWnd

0100320D  CALL DWORD PTR DS:[<&USER32.SetWindowTex>; \SetWindowTextW

As you can see, the first line store the eax register value (which is the randomized number from the first breakpoint) in the specific memory address 0x0100834C. By the way, there is no way you can tell from the code what the value of eax is, But when you actually use the debugger, at runtime, you can see this is the same value you got from the first rand function.

Back to the second breakpoint. As I said earlier the second breakpoint is being heat 52 times which is exactly the number of cards we have. So what's now interest me is where this code stores the randomize cards. So lets look at the relevant code:

010032D3  PUSH EAX                                 ; seed

010032D4  CALL DWORD PTR DS:[<&msvcrt.srand>]      ; [srand]

010032DA  POP ECX                                  ;  ebx = 52 (from earlier)

010032DB  XOR ESI,ESI                              ;  esi = 0

010032DD  /CALL DWORD PTR DS:[<&msvcrt.rand>]      ; [rand]: eax = randNumber

010032E3  |XOR EDX,EDX                             ;  edx = 0

010032E5  |DIV EBX                                 ;  edx = randNumber % ebx

010032E7  |MOV ECX,ESI                             ;  ecx = iteration #

010032E9  |AND ECX,7                               ;  ecx = ecx AND 7

010032EC  |IMUL ECX,ECX,15                         ;  ecx = ecx * 21

010032EF  |DEC EBX                                 ;  decrease iteration #

010032F0  |LEA EAX,DWORD PTR SS:[EBP+EDX*4-60]
010032F4  |MOV EDX,ESI                             ;  edx = esi

010032F6  |SHR EDX,3                               ;  edx = edx / 8

010032F9  |ADD ECX,EDX                             ;  ecx = ecx + edx

010032FB  |MOV EDX,DWORD PTR DS:[EAX]              ;  load randCardNum

010032FD  |MOV DWORD PTR DS:[ECX*4+1007554],EDX    ;  save in memory

01003304  |MOV ECX,DWORD PTR SS:[EBP+EBX*4-60]
01003308  |INC ESI                                 ;  esi = esi +1

01003309  |CMP ESI,34                              ;  check loop iterations

0100330C  |MOV DWORD PTR DS:[EAX],ECX
0100330E  \JB SHORT freecell.010032DD              ;  go to start of the loop

So, what we see in this code is calling the srand function with the seed as the game number randomize earlier. this is an important part to note. freecell has game numbers, and you can select a specific game, so how is that possible that the game is randomized but if you select over and over the same game number, you get the same cards? well, the answer lies in the first two lines of code, the "randomized" cards are randomized with the game number as a seed number to the randomize function. If the rand function is initialized with the same seed in the srand function, the randomized number it will creates will be the same.

Line number 5 is a beginning of a loop, this loops iterate 52 times, and in each iteration, randomize a card number, and puts it on the next location in the memory. The line 010032FD is where the randomized card number is saved in the memory. After tracing with the debugger a few timer on these addresses I can tell you this, there is 8 arrays of cards, the first iteration put the card number as the first item in the first array, the following iteration puts the card number in the first item of the second array, and so forth. when it gets to card number 9 it starts over, putting the card number as the second item in the first array.. the following diagram should help to better understand it:

So what's the point with these 8 array? Well, the reason we got 8 array is because freecell has 8 columns of cards. so each array is a column of cards. Again, after tracing the loop a few times I've discovered that the formula for the address where the card is saved is: CardAddress = BaseAddress + 0x54*I + 4*J, where BaseAddress is 0x1007554 (mentioned in line 010032FD), I is the column number (0..7) and J is the row number.

So, now that we got the addresses we can go on and make the Freecell Memory Reader app. The form contains 8 listbox controls, one for each column. The first thing we do is checking which operating system are we on, the freecell I've checked was on XP, I've also searched the addresses for win2K, but I couldn't test the program, so if it doesn't work on win2K, please leave a comment and I'll fix it. Anyway, I check the operating system and according to it I set the addresses I'm going to use later. Then I search the process of freecell using the static method Process.GetProcessesByName() and then I open a new instance of my ProcessMemoryReader class. This class was introduced in my previous article so check it for details explanation on the use of this class. After opening the process I read the game number from the game number address. then I loop over the cards array and for each place in the array I read its memory, convert the card number to a normal card number and place it on the proper listview (according to its column).

"Convert the card number"? Well, here I need to explain how the card is stored in memory. The number that was randomized earlier was a number between 1..52, so I need to convert it to the card's number (1..13) and the card's kind (1..4). At first I thought that the conversion is simple, I thought that it was organized like this:

Kind | Number | original number

1  - 1 - 1
1  - 2 - 2
1  - 3 - 3
1  - 4 - 4
...
2 - 1 - 14
2 - 2 - 15
...
4 - 12 - 51
4 - 13 - 52

Well, it turns out that is not the case.. instead of specifying all the cards from one kind and them the next kind and so forth (1..13,1..13,1..13,1..13) they decided to specify the sets of card numbers. (1..4,1..4,1..4, 13 times), like this:

Kind | Number | original number

1 - 1 - 1
2 - 1 - 2
3 - 1 - 3
4 - 1 - 4
1 - 2 - 5
2 - 2 - 6
...
3 - 13 - 51
4 - 13 - 52

I hope its clear now, That's why a conversion is needed. One Last thing to mention is that when I read the game number from the memory I get a 4 bytes array and I need to convert it to an Int32, there are better ways to do it then how I did but I kept my way for simplicity. So the code is as follow:

IntPtr GameNumAddress;
IntPtr CardsAddress;

// check if version is win2k or winXP

if (Environment.OSVersion.Version.Major == 5)
{
    if (Environment.OSVersion.Version.Minor == 0)    // win2K

    {
        GameNumAddress = (IntPtr)0x010071F8;
        CardsAddress = (IntPtr)0x01007f74;
    }
    else    // winXP

    {
        GameNumAddress = (IntPtr)0x0100834C;
        CardsAddress = (IntPtr)0x01007554;
    }
}
else
{
    MessageBox.Show("Sorry, only winXP and win2K are supported!");
    return;
}

// Search the Hearts Process

Process[] pArray = Process.GetProcessesByName("freecell");
if (pArray.Length == 0)
{
    MessageBox.Show("No Freecell process!");
    return;
}

// Create memory reader

ProcessMemoryReaderLib.ProcessMemoryReader pReader = 
  new ProcessMemoryReaderLib.ProcessMemoryReader();

// Take the first process found

pReader.ReadProcess = pArray[0];

pReader.OpenProcess();

for(int i=0 ; i<8 ; i++)
    listArray[i].Items.Clear();

int iGameNum;
int readBytes;
byte[] buffer;
int CardNum, CardKind;
string ItemString = "";
bool bAddCard;

buffer = pReader.ReadProcessMemory(GameNumAddress,4,out readBytes);
iGameNum = buffer[0] + 256*buffer[1] + 256*256*buffer[2] + 
  256*256*256*buffer[3];
txtGameNum.Text = iGameNum.ToString();

for (int i=0 ; i<8 ; i++)
{
    for (int j=0 ; j<21 ; j++)
    {
        buffer = pReader.ReadProcessMemory((IntPtr)(
           (int)CardsAddress + 0x54*i + 4*j),4,out readBytes);
        CardNum = (buffer[0] / 4) + 1;    
        CardKind = (buffer[0] % 4) + 1;
        bAddCard = true;

        switch (CardNum)
        {
            case 11:
                ItemString = "J";
                break;
            case 12:
                ItemString = "Q";
                break;
            case 13:
                ItemString = "K";
                break;
            case 64:
                bAddCard = false;
                break;
            default:
                ItemString = CardNum.ToString();
                break;
        }

        if (bAddCard)
            listArray[i].Items.Add(ItemString ,CardKind -1);
    }
}

pReader.CloseHandle();

On to the next game..

Section 2: Hearts, How does it work?

This game was far more difficult to debug from the rest, because of the complex ways the cards are stored in the memory. Also the example works only on XP, cause I couldn't find Hearts on a win2K system, so I couldn't search for the parallel addresses. Because of its complexity I'll will keep my explanations nice an simple, and I won't get into details.

So, how many of you knows how do I start? That's right, we open the program in the debugger (mshearts.exe) and search the file for the famous rand() function. We will find to places, and we will put a breakpoint on each place. Then we will run the program. As soon as we start a game, the first breakpoint will pop once and the second breakpoint will pop 52 times.. I think we have found the interesting part.. So as always we will look at the code and search for the memory addresses where the values are being stored. Brought to you here is the code of the second breakpoint:

01007FB6  /CALL DWORD PTR DS:[<&msvcrt.rand>]      
01007FBC  |CDQ
01007FBD  |IDIV DWORD PTR SS:[EBP-18]
01007FC0  |MOV EAX,DWORD PTR SS:[EBP-14]
01007FC3  |PUSH 0D
01007FC5  |POP EDI
01007FC6  |PUSH 4
01007FC8  |POP EBX
01007FC9  |MOV ECX,EDX
01007FCB  |CDQ
01007FCC  |IDIV EDI
01007FCE  |SUB EAX,DWORD PTR DS:[ESI+140]
01007FD4  |MOV EDI,EDX
01007FD6  |ADD EAX,4
01007FD9  |CDQ
01007FDA  |IDIV EBX
01007FDC  |LEA EAX,DWORD PTR SS:[EBP+ECX*4-110]
01007FE3  |MOV EBX,DWORD PTR DS:[EAX]
01007FE5  |SHL EDI,4
01007FE8  |LEA ECX,DWORD PTR DS:[ESI+EDX*4+130]
01007FEF  |MOV EDX,DWORD PTR DS:[ECX]
01007FF1  |MOV DWORD PTR DS:[EDI+EDX+1C],EBX
01007FF5  |MOV ECX,DWORD PTR DS:[ECX]
01007FF7  |XOR EBX,EBX
01007FF9  |DEC DWORD PTR SS:[EBP-18]
01007FFC  |INC DWORD PTR SS:[EBP-14]
01007FFF  |CMP DWORD PTR SS:[EBP-14],34
01008003  |MOV DWORD PTR DS:[EDI+ECX+28],EBX
01008007  |MOV ECX,DWORD PTR SS:[EBP-18]
0100800A  |MOV ECX,DWORD PTR SS:[EBP+ECX*4-110]
01008011  |MOV DWORD PTR DS:[EAX],ECX
01008013  \JL SHORT mshearts.01007FB6

Yea, I know, lots of ugly code.. But I've bolded the parts that interest us, the first line is where we set out breakpoint, the rand() function is being called. The second bolded line (01007FF1) is the line where the randomized value is stored in the memory. note that this is the first operation of the kind: MOV <memory address>,<value> ,So I've check carefully what is happening in this command. After tracing it a few times I've found some logic in what address are being used to store the values. The first 13 iterations of this loop (by the way, the code brought here is a loop) are accessing a zone in the memory, we will call it zone A, the next 13 iterations access a different zone in the memory, zone B, and also the next 13 iterations and the last 13 iterations. so what do we got here. 52 iterations, each 13 we change to a different zone in the memory. Also I've found that the difference between the memory addresses in each set is equal to 16. So to simplify, I've made a little diagram to better see the structure:

So, you see, we have 4 main base addresses, each one is an array, so the first item is in ZoneA + 0, the second is in ZoneA + 16 and so forth. According to the picture the 4 base addresses are stored in an array, this is correct. I've discovered that there is an array of 4 addresses, each item of that array is a pointer to another array of 13 items. All this is very nice, but I still have a problem, all of the addresses involved in this structure are dynamic, meaning they changed from run to run. I need to find an address that every time the program starts, it will always be a static pointer to the base address (the Base address is the address of the 4 pointers array). How do I know this kind of static pointer does exist? well, this involves explanations on what happens when you create a variable on the stack, and what happens when you create a variable on the heap (I'm talking about unmanaged code off course). So when you create a variable on the stack, its address is written to the file and so cannot change when you run different instances, but if you create a variable dynamically, his address is not known at design time and so cannot be written to the file. now suppose you got a pointer to an array, so the array can be in every size you want, and you cant create it dynamically and so on, BUT the pointer itself IS static and declared on the stack so there is always a need for at least a static pointer that holds the structure, and this static pointer has always the same address. So now I know it exists, how do I find it? Well, I've just did a search. I ran an instance of the program, and saw what was my Base address (the address of the array of addresses) and then I've did a search on the memory to find where is this base address is stored on the memory, and I've found only one such address. bingo. Now, the trick is to know in which region of memory to look for, cause how can you tell if the memory you are looking on is the dynamic memory or static memory? Well, the region of memory that is between 0x0100D000 and 0x0100E000. This is only for this specific file. Each file has its own .data section, and this is the section where all the static memory is stored.

If you didn't understand all the details it is not important, all you need to know is that we got a Static address = 0x0100d514, this static address is a pointer to another pointer we will call PointerArrayAddress, the PointerArrayAddress is a pointer to an array of 4 pointers. Each pointer in the array is a pointer to an array of 13 items. each item contains a card number. each array of cards is a player. and now all I need to do is read this structure and display it on the screen.

So what does out code do? First we find the Hearts process, then we read the static pointer, then we read the 4 pointers of the arrays, and then we read the 52 items out of the arrays, for each item we convert its card number into a normal representation. if you remember from the previous section the conversion to the cards, here it's the same. and finally we add it to the proper list on the screen (we got 4 lists). The code is as follows:

// Search the Hearts Process

Process[] pArray = Process.GetProcessesByName("mshearts");
if (pArray.Length == 0)
    return;

// Create memory reader

ProcessMemoryReaderLib.ProcessMemoryReader pReader =
  new ProcessMemoryReaderLib.ProcessMemoryReader();

// Take the first process found

pReader.ReadProcess = pArray[0];

pReader.OpenProcess();

int i;
int readBytes;
byte[] buffer;

IntPtr StaticAddress = (IntPtr)0x0100d514;
buffer = pReader.ReadProcessMemory(StaticAddress,4,out readBytes);
IntPtr PointerArrayAddress = (IntPtr)(
        buffer[0] + 
        256*buffer[1] + 
        256*256*buffer[2] + 
        256*256*256*buffer[3] + 0x130);

IntPtr[] aArray = new IntPtr[4];
buffer = pReader.ReadProcessMemory(
  (IntPtr)PointerArrayAddress,16,out readBytes);
for (i=0 ; i<4 ; i++)
    aArray[i] = (IntPtr)(
        buffer[4*i] + 
        256*buffer[4*i + 1] + 
        256*256*buffer[4*i + 2] + 
        256*256*256*buffer[4*i + 3] + 0x1c);
    
for (i=0 ; i<4 ; i++)
    listArray[i].Clear();

int CardNum, CardKind;
string ItemString = "";
bool bAddCard;

for (int j=0; j<13 ; j++)
{
    for (i=0 ; i<4 ; i++)
    {
        buffer = pReader.ReadProcessMemory(
          (IntPtr)((int)aArray[i] + (16 * j)),1,out readBytes);
        CardNum = (buffer[0] / 4) + 1;    
        CardKind = (buffer[0] % 4) + 1;
        bAddCard = true;

        switch (CardNum)
        {
            case 11:
                ItemString = "J";
                break;
            case 12:
                ItemString = "Q";
                break;
            case 13:
                ItemString = "K";
                break;
            case 64:
                bAddCard = false;
                break;
            default:
                ItemString = CardNum.ToString();
                break;
        }

        if (bAddCard)
            listArray[i].Items.Add(ItemString ,CardKind -1);
    }
}
pReader.CloseHandle();

In the next section I will give an example of writing to a process memory using in the Hearts game.

Section 3: WriteProcessMemory bonus

As I've said in the beginning, I could not write an article with no new feature presented so I've enhanced the functionality of the class library so now it can also write into another process memory. This is done using the API WriteProcessMemory. This function excepts a byte array, an address and a process and writes the byte array to the address of the process. Simple as that. Its normal header looks like this:

BOOL WriteProcessMemory(
    HANDLE hProcess,                // handle to process

    LPVOID lpBaseAddress,           // base of memory area

    LPCVOID lpBuffer,               // data buffer

    SIZE_T nSize,                   // count of bytes to write

    SIZE_T * lpNumberOfBytesWritten // count of bytes written

    );

So its C# equivalent is:

[DllImport("kernel32.dll")]
public static extern Int32 WriteProcessMemory(
    IntPtr hProcess, 
    IntPtr lpBaseAddress,
    [In, Out] byte[] buffer, 
    UInt32 size, 
    out IntPtr lpNumberOfBytesWritten);    
For this function to work, you need to first open the process with the access permissions PROCESS_VM_WRITE and PROCESS_VM_OPERATION.

Brought to you here is an example of using this function, the example opens the Hearts process again, only this time the goal is to write me a good game. Here is the code:

// Search the Hearts Process

Process[] pArray = Process.GetProcessesByName("mshearts");
if (pArray.Length == 0)
    return;

// Create memory reader

ProcessMemoryReaderLib.ProcessMemoryReader pReader =
  new ProcessMemoryReaderLib.ProcessMemoryReader();

// Take the first process found

pReader.ReadProcess = pArray[0];

pReader.OpenProcess();

int i;
int readBytes;
byte[] buffer;

IntPtr StaticAddress = (IntPtr)0x0100d514;
buffer = pReader.ReadProcessMemory(StaticAddress,4,out readBytes);
IntPtr PointerArrayAddress = (IntPtr)(
        buffer[0] + 
        256*buffer[1] + 
        256*256*buffer[2] + 
        256*256*256*buffer[3] + 0x130);

IntPtr[] aArray = new IntPtr[4];
buffer = pReader.ReadProcessMemory(
  (IntPtr)PointerArrayAddress,16,out readBytes);
for (i=0 ; i<4 ; i++)
    aArray[i] = (IntPtr)(
        buffer[4*i] + 
        256*buffer[4*i + 1] + 
        256*256*buffer[4*i + 2] + 
        256*256*256*buffer[4*i + 3] + 0x1c);
    
int writtenBytes;
buffer = new byte[1];

for (i=0 ; i<4 ; i++)
{
    for (int j=0; j<13 ; j++)
    {
        buffer[0] = (byte)(j*4 + i);
        pReader.WriteProcessMemory(
          (IntPtr)((int)aArray[i] + (16 * j)),buffer,out writtenBytes);
    }
}
pReader.CloseHandle();

btnReadMemory_Click(sender,null);

Note: the game might not be as much fun now. That's it. Hope you like it, Don't forget to vote.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here