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:
01003E21 >
$ 6A 70 PUSH 70 ::
Entrypoint 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:
01003F8B .
53 PUSH
EBX 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:
0100225A 897D
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).
01001FDF
> 33FF XOR
EDI,EDI ; Cases 202 (WM_LBUTTONUP) .... 01001FE1 . 393D 40510001 CMP DWORD PTR DS:[1005140],EDI 01001FE7 . 0F84 BC010000 JE winmine.010021A9 01001FED > 893D 40510001 MOV DWORD PTR DS:[1005140],EDI 01001FF3 . FF15 D8100001 CALL DWORD PTR DS:[<&USER32.ReleaseCapture>] 01001FF9 . 841D 00500001 TEST BYTE PTR DS:[1005000],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...
010037E1
$ A1 18510001 MOV EAX,DWORD PTR DS:[1005118] ::
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:[1005334] :: size of playfield in X 01003802 . 0F8F AE000000 JG winmine.010038B6 01003808 . 3B0D 38530001 CMP ECX,DWORD PTR DS:[1005338] :: 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:
01003818
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:
0100388D |.
51 PUSH
ECX ::
selected row 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:
01003896
> \8BD1 MOV
EDX,ECX ::
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:[1005160] 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