1.樣本概況
1.1 應用程式資訊
----------------------
應用程式名稱:掃雷(基于Windows xp系統)
MD5值:16A4FD569A3EB5CEBEB3DA99EF1D17E1
SHA1值:31A1A89BA067EA95F117754818429D6D8E8E59CF
簡單功能介紹:
1. 遊戲模式:分為初級,中級,進階,自定義模式
2. 自定義:可以設定遊戲界面的高度(max=24),寬度(max=30),雷數(max=667)
3. 遊戲視窗:顯示有雷數,計時,和需要探測點選的雷區
4. 遊戲規則:滑鼠點選不為雷的區域會顯示數字,提示周圍的雷數,右鍵點選可以下标記,當所有不為雷的區域被點選即成功,點選到雷則遊戲失敗,點選笑臉可以重置目前雷區
1.2 分析環境及工具
系統環境:win7 Professional 32位(15pb實驗環境)
工具:
1. DIE(PE檔案分析)
2. Cheat Engine(搜尋資料)
3. VS2019(編寫DLL程式)
4. MFCInject(dll注入工具)
5. DebugView(輸出調試資訊)
6. OllyDbg(動态調試分析)
7. Spy++(擷取視窗資訊)
8. Hash(計算MD5,SHA1)
1.3 分析目标
實作掃雷外挂:
1. 滑鼠移動懸停在雷區時,在視窗右上角顯示雷區坐标及目前區域是否有雷
2. 實作F5一鍵掃雷功能,模拟點選所有不為雷的區域完成遊戲
2.具體分析過程
2.1 DIE查殼
2.1.1 樣本基礎資訊
連結器版本:Microsoft Linker7.0
編寫工具:從連結器版本判斷為VS2003
類型:32位

2.1.2 樣本詳細資訊
分析樣本導入庫,為使用SDK編寫的程式
2.2 CE搜尋資料
2.2.1 擷取關鍵資料
擷取資料基本步驟
多次修改擷取到三個的高,寬,雷數的資料,且在CE中位址為綠色(靜态位址,全局),有多份可能為(代碼備援,實際顯示需要)
2.2.2 驗證資料有效性
在VS2019中編寫MFC DLL(靜态編譯,不依賴環境),關鍵調試代碼
- //調式擷取到的高度,寬度,雷數變量位址
- PDWORD pHeigth = (PDWORD)0x01005338;
- PDWORD pWidth = (PDWORD)0x01005334;
- PDWORD pMineCount = (PDWORD)0x01005330;
- //顯示錯誤
- BOOL CheckResult(BOOL result, WCHAR* str)
- {
- if (!result)
- {
- OutputDebugString(str);
- return FALSE;
- }
- return TRUE;
- }
- //儲存視窗句柄
- HWND g_hWnd = 0;
- //原來的回調函數
- WNDPROC OldWinProc = 0;
- //新的視窗回調函數
- LRESULT CALLBACK NewWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- switch (uMsg)
- {
- //如果是鍵盤消息
- //一鍵掃雷思路:周遊掃雷數組中的元素,判斷不為雷的區域,模拟滑鼠點選
- case WM_KEYDOWN:
- {
- //f5
- if (wParam == VK_F5)
- {
- CString str;
- str.Format(L"高:%d,寬:%d,雷數:%d\n", *pHeigth, *pWidth, *pMineCount);
- OutputDebugString(str);
- }
- }
- }
- //CallWindowProc 不處理的消息讓原來的回調函數處理
- return OldWinProc(hWnd, uMsg, wParam, lParam);
- }
- // CMFCmineApp 初始化
- BOOL CMFCmineApp::InitInstance()
- {
- CWinApp::InitInstance();
- //1. 通過查找視窗擷取視窗句柄 spy++
- //通過視窗名,視窗類名擷取視窗句柄
- g_hWnd = ::FindWindow(L"掃雷", L"掃雷");
- //判斷是否找到
- if (CheckResult(g_hWnd != 0, L"未找到掃雷視窗\n") == 0)
- return 0;
- //2. 設定視窗回調函數
- OldWinProc = WNDPROC(SetWindowLong(g_hWnd, GWL_WNDPROC, LONG(NewWndProc)));
- //判斷是否修改成功
- if (CheckResult(OldWinProc != 0, L"修改回調函數失敗\n") == 0)
- return 0;
- return TRUE;
- }
使用注入工具MFCInject注入編寫的dll,并打開ViewDebug檢視調試資訊,判斷資料有效
2.3 OD分析代碼邏輯
2.3.1 CE中找到通路資料位置
這時可以跟蹤資料,右鍵選擇是誰通路了這個資料(原理:下斷點)
這時笑臉重置按鈕就發揮作用了,點選,在CE彈出的視窗中檢視
2.3.2 OD中分析
在OD中跳轉到通路關鍵資料的位址進行分析
一共三處,其他兩處檢視與界面相關,不做具體分析,分析位址為0x01002EE4
對代碼邏輯進行分析,判斷為擷取高寬對雷區進行重置的操作,這裡可以得到雷區數組的初始位址,雷區總大小
2.3.3 擷取雷區資訊
根據記憶體視窗對照掃雷視窗區分雷區屬性(最好寬度對應)
雷區基位址:0x01005430
空白:0x0F
雷:0x8F
邊界:0x10
點選後的周圍雷數:0x40+周圍雷數
爆炸的雷:0x8A
2.3.4 調式輸出雷區數組
在DLL中編寫周遊雷區數組的代碼進行調試,關鍵代碼
- //如果是鍵盤消息
- //一鍵掃雷思路:周遊掃雷數組中的元素,判斷不為雷的區域,模拟滑鼠點選
- case WM_KEYDOWN:
- {
- //f5
- if (wParam == VK_F5)
- {
- int count = 0;
- //循環周遊整個雷區數組
- for (int y = 1; y < *pHeigth + 1; y++)
- {
- //儲存每一行雷區資訊
- CString S = 0;
- //周遊行
- for (int x = 1; x < *pWidth + 1; x++)
- {
- //擷取資訊
- PBYTE pCode = PBYTE(dwBaseAddr + y * 32 + x);
- //雷計數
- if (*pCode == MINE)
- count++;
- //存放每個位置資訊
- CString s;
- s.Format(L"%02x ", *pCode);
- S += s;
- }
- //調試輸出
- OutputDebugString(S);
- }
- //輸出雷數
- str.Format(L"雷數:%d", count);
- OutputDebugString(str);
- }
- break;
- }
ViewDebug調試資訊如下
2.3.5 查找消息回調
SPY++查找
也可以使用OD中W視窗右鍵點選視窗檢視ClassProc
或者在關鍵API下斷點回溯找到
2.3.6 分析回調函數
在OD中找到回調函數的位置,可以初步判斷消息的點選消息為WM_LBUTTONDOWN
技巧:Ctrl+A - 右鍵分析 - 假定參數選擇WndProc
設定消息斷點,選擇需要設定斷點的消息(WM_LBUTTONDWON)
回調函數原型為
LRUSULT CALLBACK WinProc(HWND hWnd, UINT uMes, Wparam wPAram, LPARAM lParam){}
參數2為消息類型,參數4為WM_LBUTTONDWON的滑鼠坐标
高位y坐标HIWORD(lParam),低位x坐标LOWORD(lParam)
消息回調中初步檢視操作了儲存滑鼠坐标lParam的代碼
該處經檢視分析為判斷點選的代碼,不做具體分析
再次找到操作WM_LBUTTONDOWN消息操作坐标的代碼,對擷取到的滑鼠坐标處理
根據處理完的值與點選的雷區界面對比,猜測為此處操作将客戶區坐标轉換為數組坐标
與掃雷視窗點選的區域對應
2.4 完成功能的實作
2.4.1 滑鼠懸停提示
在注入DLL中的消息回調響應滑鼠移動,根據分析的轉換算法将滑鼠坐标轉換成數組坐标,判斷目前雷區數組沒區域是否為雷,在标題欄提示
主要代碼如下
- //滑鼠移動顯示資訊
- case WM_MOUSEMOVE:
- {
- CString sCaption;
- //x,y坐标
- DWORD x = LOWORD(lParam);
- DWORD y = HIWORD(lParam);
- //轉換為數組坐标
- x = (x + 4) >> 4;
- y = (y - 0x27) >> 4;
- //在雷區範圍内
- if (0 < x && x <= *pWidth && 0 < y && y <= *pHeigth)
- {
- //如果判斷為雷
- if (*(PBYTE(dwBaseAddr + y * 32 + x)) == MINE)
- sCaption.Format(L"掃雷 (x:%d,y:%d) 有雷", x, y);
- else
- sCaption.Format(L"掃雷 (x:%d,y:%d) 無雷", x, y);
- SetWindowText(g_hWnd, sCaption);
- }
- }
2.4.2 F5一鍵掃雷
在注入DLL中的消息回調響應F5按鍵消息,根據分析的轉換算法将雷區數組坐标轉換成客戶區坐标,判斷目前區域是否為雷,如果不是則發送消息模拟點選完成遊戲
主要代碼如下,這裡需要注意要同時發送滑鼠左鍵按下與擡起的消息,視窗才能完成響應整個模拟點選的操作
- //如果不是雷 F5一鍵掃雷功能
- if (*pCode != MINE)
- {
- //首先将x,y數組坐标轉換為客戶區坐标
- int xPos, yPos;
- yPos = (y << 4) + 0x27;
- xPos = (x << 4) - 4;
- //前面為低位元組
- DWORD lparam = MAKELPARAM(xPos, yPos);
- //給掃雷發送消息 模拟點選
- SendMessage(g_hWnd, WM_LBUTTONDOWN, 0, lparam);
- SendMessage(g_hWnd, WM_LBUTTONUP, 0, lparam);
- }
2.5 實作效果
2.5.1 效果截圖
3.總結
很久之前玩掃雷,就想過一鍵掃雷的情景,作為第一個逆向的程式,還是從中擷取到了很多的。首先第一次系統性的分析一個檔案,從連結器資訊,編寫語言到使用的庫,關鍵的邏輯判斷及調式到實作,整個流程下來算是比較完整的,感覺逆向測試的過程還是比較需要技術的。
對逆向分析中使用的工具的了解和掌握也是比較重要的,不同的工具可以完成特定的分析功能,熟練的使用可以節約大量的時間,要讓他們成為分析時的左膀右臂,還是就是對回調函數的了解及分析,基于消息驅動使得消息處理尤為重要,是使用者與程式互動的重要樞紐,很多邏輯判斷也是在其中實作的。
作為第一次分析是沒有什麼思路的,經過視訊的講解還是在腦子裡有了個大概的過程,這次的掃雷是通過使用CE從資料入手,找到使用資料的地方,進而在OD動态分析邏輯,找到關鍵位置,再基于消息的響應處理找到是實作坐标的轉換算法,期間使用了注入和Debugview的功能完成了動态調試,進而實作了整個功能(實際還是利用原程式完成自己功能的實作,總結下就是用最小的開銷騙過程式來實作自己想要的效果)。
整體感覺還是需要多練習,基礎要紮實,這樣才可以更快的分析(基于知識和經驗的猜測)到關鍵點做判斷。