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.
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
ContinuingGreat 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 = 0x0Hmmm, 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 0x0000000000000000Well, 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
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