Win7下64位掃雷逆向以及輔助制作


 之前逆過XP下的掃雷程序,感覺XP下的掃雷很簡單,但是發現網上對於Win7下的掃雷逆向很少很少,於是就試着繼續逆一下Win7下的掃雷。這一逆發現難度提升了不只一個等級啊,經過兩天的努力,終於整個逆完了它的掃雷算法。

 首先在Win7下的掃雷不再是像XP一樣在一開始就布置好雷區,這樣我們就可以在一開始就讀取雷區內存,比較坑的是win7下的掃雷是在你點擊第一塊兒方塊時才開始布置雷區。這樣我首先在rand函數下斷點,發現有好多地方會調用rand函數,我把每個調用rand函數的地方下了斷點,然后把一直在調用的rand函數的那幾個函數斷點給去掉,這樣我們就找到了程序的突破口。

 不斷退出當前調用,並在上層函數的call調用處下斷點,直到找到了一個疑是算法入口的函數。

 跟進函數,又發現一個call,繼續跟進

 我發現這個函數便是調用rand函數的地方,估計核心就在此了,開干

 我們要對每個call都倍加小心,需要都看一下,我們發現這里好像是一個申請數組空間並填充的操作

 經過多次循環后,發現數組填充完畢,之后觀察一下申請的數組空間中存儲的東西

 發現沒有了00 01 09 0A四個值。因為我是點擊的第一個方塊,我們可以重新調試,點擊其他方塊試一下,發現這個數組會將點擊方塊周圍的的9個值去掉(包括點擊方塊自己),這樣我們就理解了,程序不會在第一次點擊方塊周圍產生雷。

 這時候我們估計就對這個程序有了一點點理解了,在點擊第一塊兒方塊的時候,程序開始申請內存。這里它會有一個結構體存儲了隨機雷數組已用大小和總空間,然后生成一個數組,並將各個雷進行編號存入數組中。之后rand函數產生的隨機雷就在這些數組中產生。接下來驗證我們的想法:

 跟進下一個call,發現這里申請數組空間,並存儲隨機出來的雷值

 

 繼續單步,發現有個小循環比較有意思

 看一下rax存儲了什么?

 這好像是存儲了多個數組的首地址啊,正好我們現在設定了9x9的雷區,這里正好9個地址,我們再跟進去這些地址看一下

 這里+10處又存儲了一個地址,繼續觀察,發現有個byte數組,存儲了雷的狀態,有雷就是1,無雷就是0

 這個時候我們就基本搞明白了這個Win7下掃雷是怎么布置的了。可是問題來了,最初的記錄雷區各個數組的地址從哪得啊?我們逆着代碼去溯源。我們發現這個值是rax+0x10處的存儲的值,而rax是rsi+0x58處存儲的值,這個rsi是rcx作為上層調用函數傳過來的參,我們走出這個函數看看這個參數從哪里得到。

我們找到了這樣一個值,在FFCFAA38中存儲了我們所想要的rsi的值。我們知道,這是一個全局變量,存儲了rsi地址。但是這個值由於RSLR機制而導致每次地址不一樣。我們有一種方法得到這個值,我們先看當前模塊加載基地址,然后用FFCFAA38(全局變量地址)-FFC500000(當前模塊加載地址)= AAA38(相對當前模塊偏移)。這樣我們可以用GetModuleHandle函數得到當前模塊加載基地址,然后加上這個偏移AAA38就得到了全局變量地址。

這樣我們就有了得到數組地址的方法:

Address = [[[[hModule+0xAAA38]+0x18]+0x58]+0x10]

這時Address就是存儲雷區數組的首地址,每個雷區地址+0x10處就是雷區列狀態數組(byte)地址。

其實,到這里我們也開始明白了,它所使用的應該是C++的vector,一個個push才產生這樣的內存空間的,不得不說,這C++功力已經爐火存青了,各種數據結構弄得頭都大了。

  找到了雷區布置數組就可以進行下一步動作了,我們通過計算鼠標坐標值來獲得雷區格子,每個格子是17*17像素大小並加上1像素的邊,所以每個格子大小為18像素,雷區邊界為30像素。你問我這些怎么得到的?這些值肯定在某個內存存着,你可以下斷點在GetCursorPos處,在你移動鼠標時會觸發斷點,然后跳出函數,發現下邊有一個GetWindowRect函數,這個函數會傳遞窗口句柄,窗口句柄存儲在一個全局內存中,我們可以得到這個窗口句柄。但是我用了更簡單的方法,既然有窗口,我直接用工具測一下就知道每個格子大小了么。

  這樣我們就得到了鼠標坐標轉換格子的公式:

int x = (xPos - 30) / 18;          //列

  int y = (yPos - 30) / 18;          //行

  最后,上輔助代碼:

WNDPROC g_oldProc = NULL;
DWORD   dwArrOffset = 0xAAA38;   //這是重定向之前全局變量相對於模塊基地址的位置
DWORD64 dwMineAddress = 0;       //雷區列數組位置
BYTE    *pMineMap;               //自定義一個數組存儲雷的布局,便於訪問
DWORD   rows = 0;                //
DWORD   cols = 0;                //
  
LRESULT CALLBACK WindowProc(
    _In_  HWND hwnd,
    _In_  UINT uMsg,
    _In_  WPARAM wParam,
    _In_  LPARAM lParam
)
{
  
    if (uMsg == WM_MOUSEMOVE)
    {
        int xPos = GET_X_LPARAM(lParam);
        int yPos = GET_Y_LPARAM(lParam);
  
        int x = (xPos - 30) / 18;          //
        int y = (yPos - 30) / 18;          //
         
        int nWidth = 30 + 18 * cols;
        int nHight = 30 + 18 * rows;
        if (x>=0&&y>=0&&x<nWidth&&y<nHight)
        {
            if (pMineMap[x*rows + y] == (BYTE)0x01)
            {
                SetWindowText(hwnd, L"!!!有雷!!!");
            }
            else
            {
                SetWindowText(hwnd, L"掃雷");
            }
        }
        else
        {
            SetWindowText(hwnd, L"掃雷");
        }
         
    }
    //F12鍵一鍵掃雷
    if (wParam == VK_F12)
    {
        int i = 0;
        do
        {
            for (int j = 0; j < rows; j++)
            {
                if (pMineMap[i*rows + j] != (BYTE)0x01)
                {
  
                    int x = (i * 18) + 30 + 9;//定位到格子中心
                    int y = (j * 18) + 30 + 9;
                     
                    LPARAM point = MAKELPARAM(x, y);
                    //發送鼠標點擊消息
                    PostMessage(hwnd, WM_LBUTTONDOWN, NULL, point);
                    PostMessage(hwnd, WM_LBUTTONUP, NULL, point);
                }
            }
            i++;
        } while (i!=cols);
    }
    return CallWindowProc(g_oldProc, hwnd, uMsg, wParam, lParam);
}
// CMineSweeperWaiguaApp 初始化
BOOL CMineSweeperWaiguaApp::InitInstance()
{
    CWinApp::InitInstance();
    OutputDebugString(L"已加載!\n");
    //獲得雷區數組地址
    HMODULE hModule = GetModuleHandle(NULL);
    // 雷區列數組獲得公式 [[[[hModule+0xAAA38]+0x18]+0x58]+0x10]
    DWORD64 v1 = *(DWORD64*)((DWORD64)hModule + dwArrOffset);
    //v2+0x8處存儲了雷總數(DWORD),v2+0x0C處存儲了行總數(DWORD),v2+0x10處存儲了列總數(DWORD)
    DWORD64 v2 = *(DWORD64*)(v1 + 0x18);
    //申請一個存儲雷區的數組空間
    rows = *(DWORD*)(v2+0x0C);     //
    cols = *(DWORD*)(v2+0x10);     //
    pMineMap = (BYTE*)VirtualAlloc(NULL,rows*cols, MEM_COMMIT, PAGE_READWRITE);
    if (pMineMap ==NULL)
    {
        OutputDebugString(L"申請內存失敗!\n");
    }
    OutputDebugString(L"申請內存成功!\n");
    //v1處存儲了列總數(DWORD)
    v1 = *(DWORD64*)(v2 + 0x58);
    dwMineAddress = *(DWORD64*)(v1 + 0x10);//存儲雷區列數組地址,有多少列就有多少數組
  
    //數組地址首位顯示的是行數
    for (int i=0;i<*((DWORD*)(v2+0x10));i++)
    {
        //v1處前4字節(DWORD)儲了行總數  ,后邊兩個內容沒搞懂(10 10)
        v1 = *(DWORD64*)(dwMineAddress + i * 8);
        BYTE *v5 = (BYTE*)(*(DWORD64*)(v1 + 0x10));
        for (int j = 0; j < *((DWORD*)(v2 + 0x0C)); j++)
        {
            //將雷區狀態賦值到數組中去 
            pMineMap[i*rows +j] = *v5;  //第i列第j行       
            v5++;
        }
    }
    OutputDebugString(L"雷區賦值成功!\n");
  
    HWND hWnd = FindWindow(NULL, L"掃雷");
    if (hWnd == NULL)
    {
        OutputDebugString(L"未找到目標窗口!\n");
        return FALSE;
    }
    //更改指定窗口的屬性
    //返回值是之前的窗口函數
    g_oldProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WindowProc);
  
    return TRUE;
}

到此,我們大功告成,需要注意的是每次要點擊一下一個格子再注入動態庫。不過,我的F12一鍵掃雷並沒成功有人知道是怎么回事么?希望不吝賜教幫我解決一下,嘻嘻~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM