掃雷外掛

        最近在瞭解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”退出外掛。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章