最近在瞭解Windows逆向工程的原理,作爲入門,寫一個最基礎的掃雷外掛,並剖析這個外掛的基礎原理,部分程序參照和借用網友的實現。
參考網友資料:https://www.52pojie.cn/thread-536250-1-1.html
https://blog.csdn.net/zhyh1435589631/article/details/61935816
說明:外掛針對的是經典的xp掃雷,對於win7、win10系統可以在這裏下載:
http://www.minesweeper.info/downloads/MinesweeperX.html .
使用的分析工具:CE 、Spy++
掃雷外掛思路: 1.通過讀取掃雷程序內存確定掃雷程序的行、列數,並得到安全位置;2.通過對遊戲窗口後臺截屏,做圖像分割確定掃雷遊戲中每個格子的位置;3.模擬鼠標對安全位置的格子發送鼠標左鍵單擊消息完成掃雷。
1.掃雷程序的內存分析
打開掃雷程序->打開CE->選擇掃雷程序->選擇查看內存
通過多次操作掃雷程序,分析出幾個地址:
0x01005340 - 雷區在內存中的地址(雷區是一個32*26字節的內存區域)
0x010056a8 - 雷區行數的地址
0x010056ac - 雷區列數的地址
0x010056a4 - 雷的個數的地址
程序中就是通過Win32 api ReadProcessMemory讀取這幾個地址得到雷區的行列數和雷區中安全位置的(行,列).
2.窗口截屏和識圖
窗口截屏是通過Win32 api FindWindow得到窗口句柄,這個函數需要的參數可以通過Spy++得到,識圖採用opencv,具體使用的函數在代碼中可以看到,主要參考https://blog.csdn.net/zhyh1435589631/article/details/61935816 中的源代碼,目的是識別出雷區網格矩形區域在窗口中的位置.
3.模擬鼠標點擊所有的安全塊
通過發送windows消息實現:
SendMessage(m_windowHandle.MineWinHandle, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
SendMessage(m_windowHandle.MineWinHandle, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
4.源代碼
GitHub:https://github.com/scuwan/SweepMineScript
//Structure.h
#pragma once
#include <string>
#include <Windows.h>
#include <iostream>
#include<iomanip>
#include<opencv2/opencv.hpp>
using namespace std;
// 窗體結構體
struct sSweepMineWindow
{
string MineWinClass;
string MineWinName;
int MineCellLen;
HWND MineWinHandle;
DWORD ProcessId;
sSweepMineWindow() :MineWinHandle(nullptr) {}
};
//掃雷內存區
struct sRowColumn
{
int r;
int c;
};
struct sSweepMineMemery
{
const int zone_adr = 0x01005340;
const int rows_adr = 0x010056a8;
const int columns_adr = 0x010056ac;
const int mines_adr = 0x010056a4;
const int len = 32 * 26;
short mine_number;
char rows;
char columns;
unsigned char mine_zone[26][32];
vector<sRowColumn> safe_row_columns;
sSweepMineMemery() { memset((void*)mine_zone, 0, 26 * 32); }
inline void Print()
{
cout << "rows: " << (int)rows << " ";
cout << "colums: " << (int)columns << " "<<endl;
cout << "mine number: " << mine_number << endl;
cout << hex<<endl;
for (int j = 0; j < 26; ++j)
{
for (int i = 0; i < 32; ++i)
{
cout <<setfill('0') << setw(2) << (int)mine_zone[j][i]<<" ";
}
cout << endl;
}
}
};
//掃雷窗口區域
struct sWindowArea
{
RECT window_area;
cv::Rect whole_area;
cv::Rect mines_area;
cv::Rect mine_num_area;
cv::Rect time_area;
cv::Rect face_area;
};
//圖像處理結構
struct sImageProcess
{
vector<vector<cv::Point>> contours;
vector<cv::Rect> rects;
};
//AutoSweepMine.h
#pragma once
#include "Structure.h"
#define Window_ClassName "Minesweeper"
#define Window_WndName "Minesweeper"
class AutoSweepMine
{
public:
AutoSweepMine();
~AutoSweepMine();
// Get窗口句柄
HWND GetWindowHwnd();
// Read 掃雷內存區域
void ReadMemry();
// Get窗口界面rectangle
void GetWindowArea();
// 截取窗口圖像
void CutWindowBmp();
// 分割窗口圖像
void SplitWindowBmp();
//// Get 窗口分割區域矩形
//void GetCountorsRect();
// 模擬鼠標點擊
void MouseClicked(int x,int y);
// 自動點擊鼠標掃雷
void AutoMouseClicked();
// 自動掃雷
void SweepMine();
private:
sSweepMineWindow m_windowHandle;
sSweepMineMemery m_memery;
sWindowArea m_windowArea;
sImageProcess m_imageProess;
};
//AutoSweepMine.cpp
#include "AutoSweepMine.h"
#include "WriteToBMP.h"
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
#define _SHOWIMAGE
#ifdef _SHOWIMAGE
#define ShowImage(WINNAME, IMAGENAME){ \
namedWindow(#WINNAME, CV_WINDOW_NORMAL); \
imshow(#WINNAME, IMAGENAME); \
cvWaitKey(); \
}
#else
#define ShowImage(WINNAME, IMAGENAME)
#endif
AutoSweepMine::AutoSweepMine()
{
m_windowHandle.MineWinClass = Window_ClassName;
m_windowHandle.MineWinName = Window_WndName;
}
AutoSweepMine::~AutoSweepMine()
{
}
HWND AutoSweepMine::GetWindowHwnd()
{
HWND hwnd = FindWindow(m_windowHandle.MineWinClass.c_str(),
m_windowHandle.MineWinName.c_str());
if (hwnd == nullptr) {
throw std::exception("沒有找到掃雷程序!!!");
}
else {
cout << "成功找到掃雷程序.\n";
}
m_windowHandle.MineWinHandle = hwnd;
DWORD process_id;
GetWindowThreadProcessId(hwnd, &process_id);
m_windowHandle.ProcessId = process_id;
cout << "掃雷程序進程id = " << process_id<<endl;
return hwnd;
}
void AutoSweepMine::ReadMemry()
{
HANDLE h_process = OpenProcess(PROCESS_VM_READ, FALSE, m_windowHandle.ProcessId);
if (nullptr == h_process)
{
throw std::exception("讀取掃雷程序內存失敗!!!");
}
SIZE_T bytes_read;
//讀取雷區
if (!ReadProcessMemory(h_process, (LPCVOID)m_memery.zone_adr, (LPVOID)m_memery.mine_zone, m_memery.len, &bytes_read))
{
throw std::exception("讀取掃雷程序內存失敗!!!");
}
//讀取行和列
if (!ReadProcessMemory(h_process, (LPVOID)m_memery.rows_adr, (LPVOID)(&m_memery.rows), 1, &bytes_read))
{
throw std::exception("讀取掃雷程序內存失敗!!!");
}
if (!ReadProcessMemory(h_process, (LPVOID)m_memery.columns_adr, (LPVOID)(&m_memery.columns), 1, &bytes_read))
{
throw std::exception("讀取掃雷程序內存失敗!!!");
}
//讀取雷的個數
if (!ReadProcessMemory(h_process, (LPVOID)m_memery.mines_adr, (LPVOID)(&m_memery.mine_number), 2, &bytes_read))
{
throw std::exception("讀取掃雷程序內存失敗!!!");
}
//提取安全的行和列
m_memery.safe_row_columns.clear();
for(int r=1;r<=m_memery.rows;++r)
for (int c = 1; c <= m_memery.columns; ++c)
{
if (m_memery.mine_zone[r][c] == 0x0F)
{
sRowColumn a;
a.r = r;
a.c = c;
m_memery.safe_row_columns.push_back(a);
}
}
//m_memery.Print();
CloseHandle(h_process);
}
void AutoSweepMine::GetWindowArea()
{
RECT rect;
bool ret = GetClientRect(m_windowHandle.MineWinHandle, &rect);
if (!ret) {
throw std::exception("獲取掃雷窗口區域失敗!!!");
}
m_windowArea.window_area = rect;
}
void AutoSweepMine::CutWindowBmp()
{
HDC hDC = ::GetDC(m_windowHandle.MineWinHandle); // 以窗口句柄爲基準進行繪製
bool ret = WriteBmp("1.bmp", hDC, m_windowArea.window_area);
::ReleaseDC(m_windowHandle.MineWinHandle, hDC);
if (!ret) {
throw std::exception("截取掃雷窗口區域圖形失敗!!!");
}
}
struct sort_index
{
int index;
int v;
};
bool cmp(sort_index a, sort_index b)
{
if (a.v > b.v)
return true;
else
return false;
}
void AutoSweepMine::SplitWindowBmp()
{
Mat src_img = imread("1.bmp");
if (src_img.empty())
{
cout << "讀取雷區圖片失敗!" << endl;
throw exception("讀取雷區圖片失敗");
}
ShowImage(src_img, src_img);
cvtColor(src_img, src_img, CV_RGB2GRAY);
threshold(src_img, src_img, 0, 255, cv::THRESH_OTSU);
ShowImage(bin, src_img);
morphologyEx(src_img, src_img, MORPH_CLOSE, Mat());
ShowImage(close, src_img);
vector<vector<Point>> contours;
findContours(src_img, contours, RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
vector<Rect> rects;
vector<vector<Point>> contours_poly(contours.size());
for (unsigned int i = 0; i <contours.size(); i++)
{
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
rects.push_back(boundingRect(contours_poly[i]));
}
Mat res(src_img.size(), CV_8UC3, Scalar(255));
RNG rng;
for (unsigned int i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(res, vector<vector<Point>>{contours[i]}, -1, Scalar(0, 0, 0), 3);
rectangle(res, rects[i].tl(), rects[i].br(), color, 2, 8, 0);
}
ShowImage(contours, res);
if (contours.size() != 5) {
throw exception("提取掃雷區域異常,請檢查!!!");
}
//提取窗口各個區域
vector<sort_index> i_v(5);
for (int i = 0; i < 5; ++i)
{
i_v[i].index = i;
i_v[i].v = rects[i].height*rects[i].width;
}
sort(i_v.begin(), i_v.end(), cmp);
m_windowArea.whole_area = rects[i_v[0].index];
m_windowArea.mines_area = rects[i_v[1].index];
m_windowArea.face_area = rects[i_v[4].index];
if (rects[i_v[2].index].x > rects[i_v[3].index].x)
{
m_windowArea.mine_num_area = rects[i_v[3].index];
m_windowArea.time_area = rects[i_v[2].index];
}
else
{
m_windowArea.mine_num_area = rects[i_v[2].index];
m_windowArea.time_area = rects[i_v[3].index];
}
m_imageProess.contours = contours;
m_imageProess.rects = rects;
}
void AutoSweepMine::MouseClicked(int x, int y)
{
SendMessage(m_windowHandle.MineWinHandle, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
SendMessage(m_windowHandle.MineWinHandle, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
}
void AutoSweepMine::AutoMouseClicked()
{
int x, y;
for (int i = 0; i < m_memery.safe_row_columns.size(); ++i)
{
x = m_windowArea.mines_area.x+m_memery.safe_row_columns[i].c * 16 - 8 + 2;
y = m_windowArea.mines_area.y+m_memery.safe_row_columns[i].r * 16 - 8 + 2;
MouseClicked(x, y);
}
}
void AutoSweepMine::SweepMine()
{
try
{
GetWindowHwnd();
ReadMemry();
GetWindowArea();
CutWindowBmp();
SplitWindowBmp();
}
catch (exception e)
{
std::cout << e.what() << endl;
return;
}
AutoMouseClicked();
}
//void AutoSweepMine::GetCountorsRect()
//{
// /*for (unsigned int i = 0; i < m_imageProess.contours.size(); i++) {
// m_imageProess.rects.push_back(boundingRect(m_imageProess.contours[i]).area());
// }*/
//}
//main.cpp
#include "AutoSweepMine.h"
#include <Windows.h>
#include <iostream>
bool RegisterHotKeys();
void UnRegisterHotKeys();
void tips();
int main(int argc, char**argv)
{
AutoSweepMine asw;
bool flag = RegisterHotKeys();
if (!flag)
{
cout << "註冊熱鍵失敗\n";
}
else
{
tips();
MSG msg = {0};
int key_id;
while (1)
{
if (GetMessage(&msg, NULL, 0, 0) != 0)
{
if (msg.message == WM_HOTKEY)
{
key_id = msg.wParam;
if(key_id==1)
{
asw.SweepMine();
}
else
{
int ok_cancel=MessageBox(NULL, "退出自動掃雷?", "消息", MB_OKCANCEL);
if (IDCANCEL == ok_cancel)
;
else
{
UnRegisterHotKeys();
break;
}
}
}
}
}
}
return (0);
}
bool RegisterHotKeys()
{
if (!RegisterHotKey(NULL, 1,MOD_ALT| MOD_NOREPEAT,0x45)) // 'e'
{
cout << "Hotkey 'ALT+e' registered false, using MOD_NOREPEAT flag\n";
cout << GetLastError();
return false;
}
if (!RegisterHotKey(NULL,2,MOD_ALT| MOD_NOREPEAT,0x51)) // 'q'
{
cout << "Hotkey 'ALT+b' registered false, using MOD_NOREPEAT flag\n";
return false;
}
return true;
}
void UnRegisterHotKeys()
{
UnregisterHotKey(NULL, 1);
UnregisterHotKey(NULL, 2);
}
void tips()
{
cout << "\"Alt+e\"" << "執行掃雷 " << "\"Alt+q\"" << "退出腳本" << endl;
}
5.外掛運行效果
打開外掛和掃雷程序:按“Alt+e”執行掃雷,“Alt+q”退出外掛。