yamh - yet another minesweeper Tool

On domingo, 12 de septiembre de 2010 0 comentarios

I, too, have written a Minsweeper Tool. I found quite a few useful functions in Minesweeper's disassembly. I wrote the solution recursively because it's easier that way. Here's a brief explanation of how I found the functions in Minesweeper's disassembly.

I used tsearch to find the location of the last cell I clicked.

1) First click on cell 1,1 and do a search for 1. Then click on cell 2,1 and continue the search for 2. Then click on cell 1,9 and continue the search for 9, etc. until you find the correct offsets for the cell you clicked. I found them at 0x1005118 and 0x1005114.

I use OllyDebug to find the function that activates a cell (when you left click a cell).

1) In olly File->Attach to Minesweeper.

2) Click the blue play button a few times until the program is not paused (Running in the bottom right corner).

3) You may need to click View->CPU (ALT+C).

4) The bottom left most window has a memory hex dump. Right click ->Go To-> Expression and enter one of the addresses you found (0x1005118 or 0x1005118 for me).

5) Click on the first byte in the hex dump. Right-click the first byte. Select Breakpoint and select Memory on Write.

6) Click on a cell (take note of which one you click), and olly should break. Mine breaks at instruction 0x100319D, yours may be different.

7) The bottom right most window shows the stack. Notice that on top of the stack a return address, and below that are the coordinates of the cell you clicked. Write down the return address (mine's 0x100340E).

I use IDA Pro to follow code cross references. Load up winmine.exe in IDA. This is where it gets tricky.

1) Jump->Jump to Address. Enter the return address (mine was 0x100340E).

2) Scroll up or down until you see the start or end of the sub routine. Double click the address (mine's sub_0_10031D4) and it will bring you to the start of the sub routine. Notice that this function has 10 arguments. Looks like a pain.

3) Search for calls to this function. Scroll to the very top of the file, click Search->Text. Enter this "call sub_0_10031D4" without the quotes. That's a call then 4 spaces then sub_0_10031D4. Your address may be different than mine.

4) When you find this call, scroll up until you find the beginning of the sub routine. The first 2 calls that you come across are in a sub routine that has a paint struct as an argument, as well as hwnd, msg, lparam, wparam. These aren't what we want. Keep searching. The third call you come across should be in sub_0_10035B7. This looks better, because there are only 4 parameters passed to the sub. At the top of the sub, however, there is a cross reference (CODE XREF: sub_0_10037E1+AE). Double click on it. This should be in sub_0_10037E1, which has no arguments! Great!

5) Inject a dll that sets the coordinates of a cell (at 0x1005118 and 0x1005114 for me), and calls the above function (0x10037E1 for me).

I used the same techniques to find the function that flags a mine, and the function the resizes the board. Sorry if it's confusing, but it seems that assembly stuff always is.

Sure I could just read which cells contain bombs, or just call the function that displays all the bombs, but this thing solves the board like a human would. There's a long delay in there (100ms). If you remove the Sleep(100), it will complete the board instantly. There's also a dll_injector and a GUI in the release folder.

Leper

*edit* Removed attachment, pasted code. Hope you like recursion.

minesweeper_Tool2.cpp

Code:
/*
Leper - 02-20-2008
Recursively solves minesweeper.
*/

#include 
#include 
#include 
#include 
#include "minesweeper_Tool2.h"
using namespace std;

#ifdef _MANAGED
#pragma managed(push, off)
#endif

/*
Populates the global board array to simplfies things.
  This function would be recursive too, but it generates a 
  call stack overflow on the largest board =)
*/
void populateBoardArray()
{
    BYTE* square = (BYTE*)0x1005361;
    // Populate the board array.
    for (int y=1; y<=*boardHeight; y++)
    {
        for (int x=1; x<=*boardWidth; x++)
        {
            board[x][y] = *square;
            square++;
            // Minesweeper evidently allocates enough room for a large board
            //   even if playing beginner.  The row continues until an 0x10
            //   (BORDER) is hit, then there are unused squares until another
            //   BORDER is found.
            if (*square==BORDER)
            {
                square++;
                while (*square != BORDER)
                    square++;
                square++;
            }
        }
    }
}

/*
Flags the given cell as a bomb.
*/
void flagCell(const int x, const int y)
{
    // If the cell is already flagged, return.
    if ( (board[x][y] & 0x0F) == FLAGGED)
        return;
    // Flag the cell, and wait for board to repaint.
    __asm
    {
        push y
        push x
        call dword ptr [flagMineFunc]
    }
    do
    {
        populateBoardArray();
    } while ( (board[x][y] & 0x0F) != FLAGGED);
}

/*
Calls the function that clears a cell.
*/
void clickCell(const int& cellX, const int& cellY)
{
    int holdX = *x;
    int holdY = *y;

    *x = cellX;
    *y = cellY;
    __asm
    {
        call dword ptr [clickCellFunc]
    }
    *x = holdX;
    *y = holdY;
    populateBoardArray();
}

/*
Clicks all cells around the given cell.
*/
void clickCells(const int& cellX, const int& cellY)
{
    // These vars. just make the code clean.
    BYTE hold;
    int holdX = *x + cellX;
    int holdY = *y + cellY;

    if (cellY > 1)
        return;
    if (cellX > 1)
    {
        clickCells(-1, cellY+1);
        return;
    }
    // Don't go off the edge of the board.
    if (*x == 1 && cellX == -1)
    {
        clickCells(cellX+1, cellY);
        return;
    }
    if (*x == *boardWidth && cellX == 1)
    {
        clickCells(-1, cellY+1);
        return;
    }
    if (*y == 1 && cellY == -1)
    {
        clickCells(-1, cellY+1);
        return;
    }
    if (*y == *boardHeight && cellY == 1)
        return;
    // No need to check cell 0,0.
    if (cellX == 0 && cellY == 0)
    {
        clickCells(cellX+1, cellY);
        return;
    }

    // If the cell in question is a cell, clear it.
    hold = board[holdX][holdY] & 0x0F;
    if (hold == CELL)
        clickCell(holdX, holdY);

    clickCells(cellX+1, cellY);
}

/*
Flags all cells around the given cell.
*/
void flagCells(const int& cellX, const int& cellY)
{
    // These vars. just make the code clean.
    BYTE hold;
    int holdX = *x + cellX;
    int holdY = *y + cellY;

    if (cellY > 1)
        return;
    if (cellX > 1)
    {
        flagCells(-1, cellY+1);
        return;
    }
    // Don't go off the edge of the board.
    if (*x == 1 && cellX == -1)
    {
        flagCells(cellX+1, cellY);
        return;
    }
    if (*x == *boardWidth && cellX == 1)
    {
        flagCells(-1, cellY+1);
        return;
    }
    if (*y == 1 && cellY == -1)
    {
        flagCells(-1, cellY+1);
        return;
    }
    if (*y == *boardHeight && cellY == 1)
        return;
    // No need to check cell 0,0.
    if (cellX == 0 && cellY == 0)
    {
        flagCells(cellX+1, cellY);
        return;
    }

    // If the cell in question is a cell, flag it.
    hold = board[holdX][holdY] & 0x0F;
    if (hold == CELL)
        flagCell(holdX, holdY);
    flagCells(cellX+1, cellY);
}

/*
Returns the number of cells a given cell is touching.
  Cells are counted wheather or no they are flagged.
*/
int touchingCells(const int& cellX, const int& cellY)
{
    BYTE hold;

    if (cellY > 1)
        return 0;

    if (cellX > 1)
        return touchingCells(-1, cellY+1);

    // Don't go off the edge of the board.
    if (*x == 1 && cellX == -1)
        return touchingCells(cellX+1, cellY);
    
    if (*x == *boardWidth && cellX == 1)
        return touchingCells(-1, cellY+1);
    
    if (*y == 1 && cellY == -1)
        return touchingCells(-1, cellY+1);
    
    if (*y == *boardHeight && cellY == 1)
        return 0;
    
    // No need to check cell 0,0.
    if (cellX == 0 && cellY == 0)
        return touchingCells(cellX+1, cellY);

    hold = board [ (*x) + cellX] [ (*y) + cellY] & 0x0F;
    if (hold == CELL || hold == FLAGGED)
        return 1+ touchingCells(cellX+1, cellY);

    return touchingCells(cellX+1, cellY);
}

/*
Returns the number of flags the given cell is touching.
*/
int touchingFlags(const int& cellX, const int& cellY)
{
    BYTE hold;

    if (cellY > 1)
        return 0;

    if (cellX > 1)
        return touchingFlags(-1, cellY+1);

    // Don't go off the edge of the board.
    if (*x == 1 && cellX == -1)
        return touchingFlags(cellX+1, cellY);
    
    if (*x == *boardWidth && cellX == 1)
        return touchingFlags(-1, cellY+1);
    
    if (*y == 1 && cellY == -1)
        return touchingFlags(-1, cellY+1);
    
    if (*y == *boardHeight && cellY == 1)
        return 0;
    
    // No need to check cell 0,0.
    if (cellX == 0 && cellY == 0)
        return touchingFlags(cellX+1, cellY);

    hold = board [ (*x) + cellX] [ (*y) + cellY] & 0x0F;
    if (hold == FLAGGED)
        return touchingFlags(cellX+1, cellY) + 1;

    return touchingFlags(cellX+1, cellY);
}

/*
Recursively solve the board.
  Returns +1 for each time a clickCells()/flagCells() is called.
*/
int solve(const int& cellX, const int& cellY)
{
    // This var. just cleans up the code some.
    BYTE hold;
    // Incremented each time flagCells()/clickCells() is called.
    int flags = 0;
    int clicks = 0;
    // How many cells and bombs the cell is touching.
    int tc; // Touching cells.
    int tf; // Touching flags.

    if (*bombs == 0)
        return 0;
    if (cellX > *boardWidth)
        return solve(1, cellY+1);
    if (cellY > *boardHeight)
        return 0;

    // If the cell at x,y is numbered between 1 and 5.
    hold = board[cellX][cellY];
    if (hold > ZERO && hold <= EIGHT)
    {
        *x = cellX;
        *y = cellY;
        // How many cells/flags the cell at x,y is touching.
        tc = touchingCells(-1, -1) + ZERO;
        tf = touchingFlags(-1, -1) + ZERO;

        // If it's touching the same number of cells as it's number
        //    (i.e. if it's a 2 and it's touching 2 cells), and all 
        //    cells are not bombs.
        if (tc == hold && tf != hold)
        {
            // Flag every cell around this one.
            flags++;
            flagCells(-1, -1);
            Sleep(SLEEPTIME);
        }

        tc = touchingCells(-1, -1) + ZERO;
        tf = touchingFlags(-1, -1) + ZERO;

        // If the number is equal to the number of bombs it's 
        //    touching, and the cell is touching other cells, 
        //    flag all surrounding cells (i.e. it's a 2, touching
        //    3 cells, 2 of which are bombs).
        if (tf == hold && tc - tf >= 1)
        {
            clicks++;
            clickCells(-1, -1);
            Sleep(SLEEPTIME);
        }
    }
    return clicks + flags + solve(cellX+1, cellY);
}

/*
If there are no solutions on the board, then a guess is necessary.
  Recursively loop through the board until an uncleard cell is found.
*/
void cheat(const int& cellX, const int& cellY)
{
    if (cellY > *boardHeight)
        return; // Never fires.
    if (cellX > *boardWidth)
    {
        cheat(1, cellY+1);
        return;
    }

    if (board[cellX][cellY] == CELL)
        clickCell(cellX, cellY);
    else
        cheat(cellX+1, cellY);
}

/*
This is basically main().  A thread is created to this function from
  this dll's entry point.
*/
DWORD WINAPI start()
{
    HANDLE hndUnload; // For uninjecting this dll.

    // Bring the window to the front, and sleep a few seconds.
    HWND hwWin = FindWindow(0, "Minesweeper");
    SetForegroundWindow(hwWin);
    Sleep(500);

    // Start the game by making a random move, then populate the board array.
    if ((*bombs))
    {
        *x = rand() % (*boardWidth) + 1;
        *y = rand() % (*boardHeight) + 1;
        __asm
        {
            call dword ptr [clickCellFunc]
        }
    }
    populateBoardArray();

    // Solve the board.
    while (*bombs > 0)
    {
        // If there are no solutions, clear the next cell (cheat).
        if ( solve(1,1) == 0)
        {
            cheat(1, 1);
            Sleep(SLEEPTIME);
        }
    }

    MessageBox(0, "Solution Found!", "Mines Sweeped", 0);

    // Unload this dll.
    hndUnload = GetModuleHandle(DLLNAME);
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, 
        (LPVOID)hndUnload, NULL, NULL);

    return 0;
}

/*
This dll's entry point.
*/
BOOL APIENTRY DllMain(HMODULE hModule, 
    DWORD ul_reason_for_call, LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
        CreateThread(0, 0, (LPTHREAD_START_ROUTINE)start, 0, 0, 0);
    return true;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif















































































































































































































































































































minesweeper_Tool2.h

Code:
/**************    GLOBALS   *************************************************/
const char DLLNAME[] = "minesweeper_Tool2.dll";
const int SLEEPTIME = 1; // In milliseconds.

// Cell info.
const int CELL = 0x0F;                    // & cell with 0x0F either bomb or cell.
const int BOMB = 0x8F;
const int FLAGGED = 0x0E;                 // cell & 0x0F leaves an 0x0E.

// How many bombs the cell is touching.
const int ZERO = 0x40;
const int ONE = 0x41;
const int TWO = 0x42;
const int THREE = 0x43;
const int FOUR = 0x44;
const int FIVE = 0x45;
const int SIX = 0x45;
const int SEVEN = 0x45;
const int EIGHT = 0x45;
const int BORDER = 0x10;                  // Commented in populateBoardArray().

// Board info, and gloabal board array/
const int MAXHEIGHT = 24;                 // MAX board height/width.
const int MAXWIDTH = 30;
BYTE* boardHeight = (BYTE*)0x10056A8;     // Actual board height/width.
BYTE* boardWidth = (BYTE*)0x10056AC;
BYTE board[MAXWIDTH+1][MAXHEIGHT+1];      // Ignore element [0][0].

// Functions found with ida and TSearch.
DWORD clickCellFunc = 0x10037E1;          // Called when a cell is clicked.
DWORD flagMineFunc = 0x100374F;           // Called when a cell is right clicked.
DWORD resizeBoardFunc = 0x100367A;        // Called when the board is resized.

// Offsets found with TSearch.
BYTE* square = (BYTE*)0x1005361;          // The first square in memory.
int* x = (int*)0x1005118;                 // The location of the square to
int* y = (int*)0x100511C;                 //     click (with clickCell).
int* bombs = (int*)0x1005194;             // The number of bombs displayed.
/*****************************************************************************/

/********************* PROTOTYPES ********************************************/
void populateBoardArray();
void flagCell(const int x, const int y);

0 comentarios:

Publicar un comentario