This small project examines the memory of another process and it prints a map with all mines exposed. The source code is at Github Gist.

mines example When you click on a cell the game checks whether you’re safe or not. So, the mouse click triggers an event that calls a function to make that check. Using the command strings we can find the function names available.

    % strings /usr/bin/gnome-mines
    ...
    minefield_clear_mine
    minefield_multi_release
    minefield_is_location
    minefield_is_cleared
    minefield_has_mine    <=====
    minefield_is_clock_started
    return_value != NULL
    completedField
    ...

The amount of printed strings is scary. Yet, after a quick look, we find very promising names such as minefiled_has_mine. If that’s a function it needs to know where the mines are and our job is to know how to locate them and read them.

    % gnome-mines&
    [1] 28978
    % gdb -p 28978
    (gdb) disassemble minefield_has_mine 
    Dump of assembler code for function minefield_has_mine:
       0x000056551241db30 <+0>:	endbr64 
       0x000056551241db34 <+4>:	test   %rdi,%rdi
       0x000056551241db37 <+7>:	je     0x56551241db50 <minefield_has_mine+32>
       0x000056551241db39 <+9>:	imul   0x3c(%rdi),%esi
       0x000056551241db3d <+13>:	mov    0x30(%rdi),%rax
       0x000056551241db41 <+17>:	add    %esi,%edx
       0x000056551241db43 <+19>:	mov    (%rax,%rdx,8),%rax
       0x000056551241db47 <+23>:	mov    0x20(%rax),%eax
       0x000056551241db4a <+26>:	retq
       0x000056551241db4b <+27>:	nopl   0x0(%rax,%rax,1)
       0x000056551241db50 <+32>:	sub    $0x8,%rsp
       0x000056551241db54 <+36>:	lea    0x6829(%rip),%rdx        # 0x565512424384
       0x000056551241db5b <+43>:	lea    0x7cde(%rip),%rsi        # 0x565512425840
       0x000056551241db62 <+50>:	xor    %edi,%edi
       0x000056551241db64 <+52>:	callq  0x565512417390 <g_return_if_fail_warning@plt>
       0x000056551241db69 <+57>:	xor    %eax,%eax
       0x000056551241db6b <+59>:	add    $0x8,%rsp
       0x000056551241db6f <+63>:	retq
    End of assembler dump.
    
    (gdb) b *0x000056551241db39
    Breakpoint 1 at 0x56551241db39
    (gdb) c
    Continuing

Great minefield_has_mine is a function indeed! By clicking on a cell the game should call that function and GDB will luckily stop at the required point.

    Thread 1 "gnome-mines" hit Breakpoint 1, 0x000056551241db39 in minefield_has_mine ()
    (gdb) display/i $pc
    1: x/i $pc
    => 0x56551241db39 <minefield_has_mine+9>:	imul   0x3c(%rdi),%esi
    (gdb) x /gx $rdi + 0x3c
    0x56551330cccc:	0x0000000000000008
    (gdb) print /x $esi
    $1 = 0x0

Hmmm, it’s multiplying $rdi+0x3c = 8 with $esi = 0. At first sight, it seems that $rdi+0x3c is the number of lines in the board and $esi is the selected column cell. After the multiplication $esi will store the skipped cells to reach to the required cell.

Let’s take a deeper look at $rdi, it seems to have more information for us.

    (gdb) x /30gx $rdi
    0x56551330cc90:	0x000056551316a5a0	0x0000000000000004
    0x56551330cca0:	0x00005655133c7be0	0x000056551330cc70
    0x56551330ccb0:	0x0000000800000008	0x000000000000000a
    0x56551330ccc0:	0x00005655131327c0	0x0000000800000008
    0x56551330ccd0:	0x0000000100000000	0x0000000000000000
    0x56551330cce0:	0x00007f19aac74450	0x0000001c00000004
    0x56551330ccf0:	0x0000000000000000	0x0000000000000000
    0x56551330cd00:	0x000056551332ee40	0x0000565512fa49e0
    0x56551330cd10:	0x0000000000000000	0xffffffffffffffff
    0x56551330cd20:	0x00000000ffffffff	0x0000000000000000
    0x56551330cd30:	0x0000565512e76170	0x0000000000000001
    0x56551330cd40:	0x0000000000000000	0x000056551330cce0
    0x56551330cd50:	0x0000565512d46020	0x0000000000001202
    0x56551330cd60:	0x0000565512d90a40	0x000056551330cb30
    0x56551330cd70:	0x000056551332ecc0	0x0000000000000000

Well, the register seems to have the address of the main game object. At 0x56551330ccb0 we can find the board size (8x8) and the total number of mines (0xa => 10).

    (gdb) ni
    0x000056551241db3d in minefield_has_mine ()
    1: x/i $pc
    => 0x56551241db3d <minefield_has_mine+13>:	mov    0x30(%rdi),%rax
    (gdb) x /gx $rdi+0x30
    0x56551330ccc0:	0x00005655131327c0
    COMMENT: $rdi+0x30 is an address (possibly pointing to another object).
    
    (gdb) ni
    0x000056551241db41 in minefield_has_mine ()
    1: x/i $pc
    => 0x56551241db41 <minefield_has_mine+17>:	add    %esi,%edx
    (gdb) print /x $edx
    $2 = 0x0
    (gdb) print /x $esi
    $3 = 0x0
    COMMENT: $edx has the selected cell line number, it sums to the $esi found above to have the offset to required cell.
    
    (gdb) ni
    0x000056551241db43 in minefield_has_mine ()
    1: x/i $pc
    => 0x56551241db43 <minefield_has_mine+19>:	mov    (%rax,%rdx,8),%rax
    COMMENT: copy the content of [$rdx * 8 + $rax] to $rax. That 8 has nothing to do with the map, it´s possible the number of bytes between different objects of that map.
    
    (gdb) ni
    0x000056551241db47 in minefield_has_mine ()
    1: x/i $pc
    => 0x56551241db47 <minefield_has_mine+23>:	mov    0x20(%rax),%eax
    COMMENT: $rax+0x20 is our cell: 0 if empty, otherwise 1. Copying it to $eax and retuning (next instruction) will return this value.
    
    (gdb) ni
    0x000056551241db4a in minefield_has_mine ()
    1: x/i $pc
    => 0x56551241db4a <minefield_has_mine+26>:	retq

mines explained Now the job is to translate that assembly code to C language, which is quite straightforward. My code uses the initial heap address and loops the whole range looking for the [row, column, mine]. Then, we need to use the same offsets to access the map.

    base frame = {stoi(argv[2]), stoi(argv[3]), stoi(argv[4])};
    auto address = search_memory(fd, addresses, frame);
    if (address == 0) {
        cerr << "cannot find the board in memory.\n";
        return 4;
    }
    
    pread(fd, &location, sizeof(uint64_t), static_cast<off_t>(address + 0x30));
    pread(fd, &mine_pos, sizeof(int32_t), static_cast<off_t>(address + 0x3c));
    
    for (int i = 0; i < frame.height; ++i) {
        for (int j = 0; j < frame.width; ++j) {
            auto index = 8 * (mine_pos * j + i);
            pread(fd, &has_mine_p, sizeof(uint64_t),
                    static_cast<off_t>((location + index)));
            pread(fd, &has_mine, sizeof(int32_t),
                    static_cast<off_t>(has_mine_p + 0x20));
            cout << has_mine << " ";
        }
        cout << endl;
    }

How to use it

    % g++ -std=c++17 -g3 mines.cpp -o mines
    % gnome-mines&
    [1] 13264
    
    % ./mines 13264 16 16 40
    0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0
    0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0
    1 1 0 0 0 0 0 0 1 0 1 0 0 1 0 1
    1 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0
    1 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1
    0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
    0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0
    0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0
    0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
    0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 0
    0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
    0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0

mines final