Cheating Minesweeper - by Sunshine
1 - Introduction
Today we gonna have a look at the Windows game 'Minesweeper'. Let's see if we can make it that we always win. :-)
|Tools used:||Ollydbg (I use Shadow's MOD from version 1.10)|
|A C Compiler (if you wanna recompile my loader)|
|Knowledge of Windows API and 32bit Assembly (!)|
You find Minesweeper in your system32 folder in your Windows directory, the file is called winmine.exe (if you installed these games with your windows installation). My file version of winmine.exe is 5.1.2600.0 (I have WinXP with service pack 2 (German)), so if you have another version, the offsets and memory locations can be different.
2 - First look at the target...
We have a gamefield
of choosable width and height, also the number of mines we can define. Goal
is to open all fields without a mine; as soon as we select a mine, we loose.
Note that a field is uncovered when we release the mousebutton and not if we
press it; that means we have to look at the WM_LBUTTONUP message.
Let's think how we would code the gamefield as a programmer (at least as I would do it :-) : as data structure we would probably use a one- or two-dimensional array of some type, perhaps an integer, defining each possible state with a special value (e.g MINE=0, EMPTY=1 and so on...). That means at start of a new game a memory block of special size (most probably proportional to width*height of gamefield) is allocated and mines are placed either randomly or with help of an special algorithm. On every release of the left mousebutton, we look if the cursor is over a single field, then we look at the corresponding element in our array. If it was a mine -> gameover, otherwise we would calculate the number to inform the player about near mines. Finally we repaint the gamefield and that's it.
Ok, with that knowledge in mind we can start the real part...
3 - Let's begin...
Ok, start Ollydbg, open a backup copy of winmine.exe and begin tracing:
$ 6A 70 PUSH 70 ::
01003E23 . 68 90130001 PUSH winmine.01001390
01003E28 . E8 DF010000 CALL winmine.0100400C :: install SE Handler
After some checks and commandline parsing we step into the following call which starts the real game:
01003F8C . 53 PUSH EBX
01003F8D . FFD7 CALL EDI :: kernel32.GetModuleHandleA
01003F8F . 50 PUSH EAX ; |Arg1
01003F90 . E8 5BE2FFFF CALL winmine.010021F0 :: <- Main call (step into)
Stepping into this call, we see normal startup code like initiation of common controls and loading icons and cursors; after that we see that a window class is registered with 'RegisterClass', the main window is created and the application enters the message loop. So we have to find out where the messages are processed. As we know, the address of the window procedure is part of the WNDCLASS structure passed to the RegisterClass call. Have a look:
B4 MOV DWORD PTR SS:[EBP-4C],EDI
0100225D C745 B8 C91B0 MOV DWORD PTR SS:[EBP-48],winmine.01001BC9 <- lpfnWndProc
01002264 897D BC MOV DWORD PTR SS:[EBP-44],EDI
01002267 897D C0 MOV DWORD PTR SS:[EBP-40],EDI
01002283 8D45 B4 LEA EAX,DWORD PTR SS:[EBP-4C] :: eax = ptr to wndclass
0100228B . 50 PUSH EAX ; :: push that pointer
01002292 . FF15 CC100001 CALL DWORD PTR DS:[<&USER32.RegisterClassW>];RegisterClassW
For the beginners: How do we know that address 1001BC9 is our wanted window procedure? Well, very often it's a hardcoded address like in our case. But to be more sure, we can look at the definition of the WNDCLASS structure in MSDN. There we see that the pointer to the window procedure is the second member in this structure: here in our case the first member is in [EBP-4C], so in [EBP-48] must be second (the first member is of type UINT and so 4 bytes long).
> 33FF XOR
EDI,EDI ; Cases 202 (WM_LBUTTONUP) ....
01001FE1 . 393D 40510001 CMP DWORD PTR DS:,EDI
01001FE7 . 0F84 BC010000 JE winmine.010021A9
01001FED > 893D 40510001 MOV DWORD PTR DS:,EDI
01001FF3 . FF15 D8100001 CALL DWORD PTR DS:[<&USER32.ReleaseCapture>]
01001FF9 . 841D 00500001 TEST BYTE PTR DS:,BL
01001FFF . 0F84 B6000000 JE winmine.010020BB
01002005 . E8 D7170000 CALL winmine.010037E1 :: Process Game AI
0100200A . E9 9A010000 JMP winmine.010021A9
The call at 1002005 is the only one which is processed when a mousebutton is released, so here must be processed the game logic. Step into it...
$ A1 18510001 MOV EAX,DWORD PTR DS: ::
selected field in X
010037E6 . 85C0 TEST EAX,EAX
010037E8 . 0F8E C8000000 JLE winmine.010038B6
010037EE . 8B0D 1C510001 MOV ECX,DWORD PTR DS:[100511C] :: field in Y direction
010037F4 . 85C9 TEST ECX,ECX
010037F6 . 0F8E BA000000 JLE winmine.010038B6
010037FC . 3B05 34530001 CMP EAX,DWORD PTR DS: :: size of playfield in X
01003802 . 0F8F AE000000 JG winmine.010038B6
01003808 . 3B0D 38530001 CMP ECX,DWORD PTR DS: :: size of playfield in Y
0100380E . 0F8F A2000000 JG winmine.010038B6
01003814 . 53 PUSH EBX
At 1005118 the
selected field in x-direction is saved, in 100511C the one in y-direction. (Note:
To find that out, you can rightclick one of these lines and select 'Find reference
to address constant'. Only at three locations a value is written to this address;
with some effort we find out that at 10031DD/10031E4 on every mouse move the
fields under the cursor are written to these locations).
Both values are checked for validity: if they are greater than zero and smaller than the actual width/height.
Some lines below we have:
833D A4570001 00 CMP DWORD PTR DS:[10057A4],0 ::
first field clicked?
After some runnings, it's easy to find out that at 10057A4 a flag is stored saving if it's the first player's try of current game. If so, we don't jump but do some initialization things like starting the game time. Going on, we discover following lines:
0100388E |. 50 PUSH EAX :: selected column
0100388F |. E8 23FDFFFF CALL winmine.010035B7
Seems promising at first, but this call is not always executed. Step over it and see what coming next:
> \8BD1 MOV
edx = row of selected field
01003898 . C1E2 05 SHL EDX,5 :: row = row * 32
0100389B . 8A9402 40530001 MOV DL,BYTE PTR DS:[EDX+EAX+1005340]
-> dl = 1005340 + (row * 32) + column
010038A2 . F6C2 40 TEST DL,40
010038A5 . 75 0F JNZ SHORT winmine.010038B6
010038A7 . 80E2 1F AND DL,1F
010038AA . 80FA 0E CMP DL,0E
010038AD . 74 07 JE SHORT winmine.010038B6
010038AF . 51 PUSH ECX :: push row
010038B0 . 50 PUSH EAX :: push column
010038B1 . E8 5CFCFFFF CALL winmine.01003512 :: game logic
010038B6 > FF35 60510001 PUSH DWORD PTR DS:
010038BC . E8 52F0FFFF CALL winmine.01002913 :: paint routine
Ok here is the
important part: main purpose of the call at 10038BC is painting the changed
gamefield to screen. The call at 10038B1 does the main game logic, e.g it checks
again if it's the first try (if so it handles a special case as you may have
noticed that on your first try you never hit a mine) and calculates the new
field, dependent if the player hit a mine or not.
But let's have a look at the lines in front of this call: the selected column and the selected row multiplied by 32 are added to an address constant. And I can tell you, this calculation also happens quite often inside the game logic call. Follow 1005340 in dump and .... we see this:
It looks as we have found the memory location where the gamefield
data is stored. If you want you can change the width, height and number of mines
in Minesweeper to become sure that this is really the actual gamefield data....
and yes it is :-)
Well, in fact that's all we have to know: cause the address is a constant, this data is always(!) stored at 1005340 which makes things much more easier than to handle with pointers to changeable memory places. To access a special field, you just do: field = 0x1005340 + (row * 32) + column. 0x8F means there is a mine, 0x0F means it's still uncovered, 0x40 it's uncovered.... the rest you can find out by yourself if you want but it's not necessary...
So what to do know? First I thought of patching, but it seems a bit complicated to me. We would have to check if the player has hit a mine and if so we could redirect the program flow that the field would stay uncovered. But that means inserting additional code into a cave, but what's more a problem is the fact that we have to change also the paint routine in this case (I tried some patching and got some strange graphic errors...). And we have seen that several game states/options etc. are saved somewhere in memory (e.g. is it the first player's try? Is a mouse button currently pressed? And so on...). Probably we would have to take a look at them, too in order to keep them valid.
That's why I have decided to code a little loader which doesn't change the Minesweeper file in any form. The loader starts minesweeper (or attaches a running instance), reads periodically the memory at our found address (0x1005340) and paints the current gamefield, including all mines of course. Have a look at the source, it's quite easy and well commented. Here a screenshot where you can see the loader in action:
I hope you found this little
article and my source useful :-) C u again...
Questions, criticism? Mail me!
Sunshine, March 2006
This site is part of Sunshine's Homepage