Skip to main content

Minesweeper

Reverse engineering Hacking

Target Information
#

  • Architecture: x86_32
  • OS: Windows
  • Game: Minesweeper

Objective
#

Find a way to reveal all the mines

Tools
#

  • x32dbg

Process
#

How does the game draw the mines?

My objective is to reveal all mines in the start of a game. I presumed a good place to start would be to look at function calls related to the games graphics, but this idea fell flat. There are at least +25 functions being used from the GDI32 library (Graphics library the game uses). I quickly got overwhelmed by information and realized I needed a new approach to narrow the search.

While playing the game, I noticed when you click on a mine it would trigger an explosion sound then the game would display all the mines. So, with that in mind I formulated a new question…

How does the game call the function for the explosion sound?

I figured if I can find the function responsible for playing sounds, I should be able to traverse the game code from that point of reference. Because remember, during gameplay the logic is as follows:

Click on a mine > play audio > display mines

OR

click on a mine > display mines > play audio

I’m not entirely sure which function comes first because game execution makes it seem simultaneously. Using a debugger will be helpful here, since it will allow us to look over each instruction, line by line.

Using x32dbg, I begin the search for intermodular calls. This will provide me with a listing of all OS functionality the game uses. The search is still broad at this point, but I am looking for anything that might point me in the right direction.

MScalls

Address=010038C8
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

Address=010038E6
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

Address=01003937
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

These disassembly instructions look interesting. Let’s set a breakpoint on these addresses to see how the game uses <PlaySoundW>, hopefully it relates to the explosion noise I’ve been hearing when clicking on a mine.


Reverse Engineering
#

Now, with the breakpoints set, all we have to do is play the game with the intent of clicking on a mine and see if anything gets triggered.

Address 0x010038C8
#

Address=010038C8
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

The breakpoint on this address was not triggered at all during gameplay. For now, I will skip this function and move on to the next two.

Address 0x010038E6
#

Address=010038E6
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

Again, the breakpoint was not triggered during gameplay, moving on to the last function.

Address 0x01003937
#

Address=01003937
Disassembly=call dword ptr ds:[<&PlaySoundW>]
Destination=<winmm.PlaySoundW> (71E82D80)

Breakpoint for Address 0x01003937 gets triggered during gameplay due to the clock ticking audio. I had to unbreak this address or else the game is unplayable due to pausing every second. Out of the 3 addresses, this breakpoint was the only that was broken.

It would appear that the 3 addresses in question were of no help. But, I did see something interesting from our 3rd breakpoint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
010038EC | C3                    | ret                                               |
010038ED | 833D B8560001 03      | cmp dword ptr ds:[10056B8],3                      | conditional play sounds
010038F4 | 75 47                 | jne winmine__xp.100393D                           |
010038F6 | 8B4424 04             | mov eax,dword ptr ss:[esp+4]                      |
010038FA | 48                    | dec eax                                           |
010038FB | 74 2A                 | je winmine__xp.1003927                            |
010038FD | 48                    | dec eax                                           |
010038FE | 74 15                 | je winmine__xp.1003915                            |
01003900 | 48                    | dec eax                                           |
01003901 | 75 3A                 | jne winmine__xp.100393D                           |
01003903 | 68 05000400           | push 40005                                        |
01003908 | FF35 305B0001         | push dword ptr ds:[1005B30]                       |
0100390E | 68 B2010000           | push 1B2                                          |
01003913 | EB 22                 | jmp winmine__xp.1003937                           |
01003915 | 68 05000400           | push 40005                                        |
0100391A | FF35 305B0001         | push dword ptr ds:[1005B30]                       |
01003920 | 68 B1010000           | push 1B1                                          |
01003925 | EB 10                 | jmp winmine__xp.1003937                           |
01003927 | 68 05000400           | push 40005                                        |
0100392C | FF35 305B0001         | push dword ptr ds:[1005B30]                       |
01003932 | 68 B0010000           | push 1B0                                          |
01003937 | FF15 68110001         | call dword ptr ds:[<&PlaySoundW>]                 |

The assembly bit above is a function frame and the last line 0x1003937 is our 3rd breakpoint location that we triggered. In this frame, we can see conditional branching to function <PlaySoundW>, you can also see the different sets of arguments being passed for a specific condition. Let’s organize the data into a flow chart to get a different visual.

flowchart TD linkStyle default stroke:green; fun[Call PlaySoundW] par1[push 4005 push dword ptr ds: 1005B30 push 1BO] par2[push 4005 push dword ptr ds: 1005B30 push 1B1] par3[push 4005 push dword ptr ds: 1005B30 push 1B2] par1-- clock noise ---fun par2-- Unknown (Win?) ---fun par3-- lose ---fun

I set more breakpoints on lines 11,15, and 19 to test each one, I picked these lines because they are arguments being passed to the function call.

Line 11 gets triggered from clicking on a mine.

Line 15 never gets triggered during gameplay (I’m assuming this is for a win condition, since the only way I’ve been playing is by playing to lose).

Line 19 gets triggered from game clock audio.

MScalls
Line 11 breakpoint triggered

This is the game state with a triggered breakpoint on line 11 (the lose condition). Visually, I know now that the reveal mines function is called before <PlayingSoundW>.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
010038EC | C3                    | ret                                              
010038ED | 833D B8560001 03      | cmp dword ptr ds:[10056B8],3                     
010038F4 | 75 47                 | jne winmine__xp.100393D                         
010038F6 | 8B4424 04             | mov eax,dword ptr ss:[esp+4]            
010038FA | 48                    | dec eax                                 
010038FB | 74 2A                 | je winmine__xp.1003927             
010038FD | 48                    | dec eax                                 
010038FE | 74 15                 | je winmine__xp.1003915                  
01003900 | 48                    | dec eax                                  
01003901 | 75 3A                 | jne winmine__xp.100393D                           
01003903 | 68 05000400           | push 40005                            
01003908 | FF35 305B0001         | push dword ptr ds:[1005B30]                       
0100390E | 68 B2010000           | push 1B2                                          
01003913 | EB 22                 | jmp winmine__xp.1003937                           
01003915 | 68 05000400           | push 40005                                       
0100391A | FF35 305B0001         | push dword ptr ds:[1005B30]                       
01003920 | 68 B1010000           | push 1B1                                          
01003925 | EB 10                 | jmp winmine__xp.1003937                          
01003927 | 68 05000400           | push 40005                                       
0100392C | FF35 305B0001         | push dword ptr ds:[1005B30]                       
01003932 | 68 B0010000           | push 1B0                                          
01003937 | FF15 68110001         | call dword ptr ds:[<&PlaySoundW>]                 

Line 2 is the beginning of the function, let’s get references to this line. By doing this, we will find where we previously were before calling Function 0x010038ED

1
2
3
Address = 01003002  | Disassembly = call winmine__xp.10038ED
Address = 010034CF  | Disassembly = call winmine__xp.10038ED
Address = 0100382B  | Disassembly = call winmine__xp.10038ED

The results are 3 different addresses (Think of conditional jumps to 0x010038ED). Again, we have to step into each code construct and find out what they do.

Reference 1
#

1
2
3
4
5
6
7
8
9
01002FDD | C2 0400               | ret 4
01002FE0 | 833D 64510001 00      | cmp dword ptr ds:[1005164],0
01002FE7 | 74 1E                 | je winmine__xp.1003007
01002FE9 | 813D 9C570001 E703000 | cmp dword ptr ds:[100579C],3E7
01002FF3 | 7D 12                 | jge winmine__xp.1003007
01002FF5 | FF05 9C570001         | inc dword ptr ds:[100579C]
01002FFB | E8 B5F8FFFF           | call winmine__xp.10028B5
01003000 | 6A 01                 | push 1
01003002 | E8 E6080000           | call winmine__xp.10038ED

Using the same process, I set a breakpoint on line 2 0x1002FE0 the game halts execution due to the game clock ticking. What this tells me is that the subsequent lines of instructions have nothing to do with the game lose condition. So, we can stop our search here and exclude this path of logic. Which leads us with two more conditional logic paths for us to examine.

Reference 2
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
01003479 | C2 0400               | ret 4
0100347C | 8325 64510001 00      | and dword ptr ds:[1005164],0
01003483 | 56                    | push esi
01003484 | 8B7424 08             | mov esi,dword ptr ss:[esp+8]
01003488 | 33C0                  | xor eax,eax
0100348A | 85F6                  | test esi,esi
0100348C | 0F95C0                | setne al
0100348F | 40                    | inc eax
01003490 | 40                    | inc eax
01003491 | 50                    | push eax
01003492 | A3 60510001           | mov dword ptr ds:[1005160],eax
01003497 | E8 77F4FFFF           | call winmine__xp.1002913
0100349C | 33C0                  | xor eax,eax
0100349E | 85F6                  | test esi,esi
010034A0 | 0F95C0                | setne al
010034A3 | 8D0485 0A000000       | lea eax,dword ptr ds:[eax*4+A]
010034AA | 50                    | push eax
010034AB | E8 D0FAFFFF           | call winmine__xp.1002F80
010034B0 | 85F6                  | test esi,esi
010034B2 | 74 11                 | je winmine__xp.10034C5
010034B4 | A1 94510001           | mov eax,dword ptr ds:[1005194]
010034B9 | 85C0                  | test eax,eax
010034BB | 74 08                 | je winmine__xp.10034C5
010034BD | F7D8                  | neg eax
010034BF | 50                    | push eax
010034C0 | E8 A5FFFFFF           | call winmine__xp.100346A
010034C5 | 8BC6                  | mov eax,esi
010034C7 | F7D8                  | neg eax
010034C9 | 1BC0                  | sbb eax,eax
010034CB | 83C0 03               | add eax,3
010034CE | 50                    | push eax
010034CF | E8 19040000           | call winmine__xp.10038ED

In this code segment, there is multiple calls (Lines 12,18,26,32).

We can exclude line 32 because that path leads us to <PlaySoundW>.

To check each call we will set a breakpoint on the first one and then step-over it. Stepping-over the function will keep us from entering another function frame. We just want to see the functionality, not the details.

debugging
Line 12 breakpoint triggered
I set a breakpoint on line 12 0x01003497 and play the game to get the lose condition.

Game halts execution before calling the function and we can see only one mine being displayed. I step-over the function, but one mine is still displayed.

I move onto the next call, line 18. The results are what I wanted to see…

Finding revealMines()
#

debugging2
Located revealMines()

Bingo! Looks like we found the function!

The call to winmine_xp.1002F80 is the function responsible for revealing the mines. For a quick test, you can set the EIP to address 0x01002F80 and the debugger will execute the function.

Using, C++ I made a DLL containing a function pointer to the address of revealMines() and injected the DLL into the game’s process.

Calling function to reveal mines via DLL injection