Win7下64位扫雷逆向以及辅助制作
之前逆過XP下的掃雷程序,感覺XP下的掃雷很簡(jiǎn)單,但是發(fā)現(xiàn)網(wǎng)上對(duì)于Win7下的掃雷逆向很少很少,于是就試著繼續(xù)逆一下Win7下的掃雷。這一逆發(fā)現(xiàn)難度提升了不只一個(gè)等級(jí)啊,經(jīng)過兩天的努力,終于整個(gè)逆完了它的掃雷算法。
首先在Win7下的掃雷不再是像XP一樣在一開始就布置好雷區(qū),這樣我們就可以在一開始就讀取雷區(qū)內(nèi)存,比較坑的是win7下的掃雷是在你點(diǎn)擊第一塊兒方塊時(shí)才開始布置雷區(qū)。這樣我首先在rand函數(shù)下斷點(diǎn),發(fā)現(xiàn)有好多地方會(huì)調(diào)用rand函數(shù),我把每個(gè)調(diào)用rand函數(shù)的地方下了斷點(diǎn),然后把一直在調(diào)用的rand函數(shù)的那幾個(gè)函數(shù)斷點(diǎn)給去掉,這樣我們就找到了程序的突破口。
不斷退出當(dāng)前調(diào)用,并在上層函數(shù)的call調(diào)用處下斷點(diǎn),直到找到了一個(gè)疑是算法入口的函數(shù)。
跟進(jìn)函數(shù),又發(fā)現(xiàn)一個(gè)call,繼續(xù)跟進(jìn)
我發(fā)現(xiàn)這個(gè)函數(shù)便是調(diào)用rand函數(shù)的地方,估計(jì)核心就在此了,開干
我們要對(duì)每個(gè)call都倍加小心,需要都看一下,我們發(fā)現(xiàn)這里好像是一個(gè)申請(qǐng)數(shù)組空間并填充的操作
經(jīng)過多次循環(huán)后,發(fā)現(xiàn)數(shù)組填充完畢,之后觀察一下申請(qǐng)的數(shù)組空間中存儲(chǔ)的東西
發(fā)現(xiàn)沒有了00 01 09 0A四個(gè)值。因?yàn)槲沂屈c(diǎn)擊的第一個(gè)方塊,我們可以重新調(diào)試,點(diǎn)擊其他方塊試一下,發(fā)現(xiàn)這個(gè)數(shù)組會(huì)將點(diǎn)擊方塊周圍的的9個(gè)值去掉(包括點(diǎn)擊方塊自己),這樣我們就理解了,程序不會(huì)在第一次點(diǎn)擊方塊周圍產(chǎn)生雷。
這時(shí)候我們估計(jì)就對(duì)這個(gè)程序有了一點(diǎn)點(diǎn)理解了,在點(diǎn)擊第一塊兒方塊的時(shí)候,程序開始申請(qǐng)內(nèi)存。這里它會(huì)有一個(gè)結(jié)構(gòu)體存儲(chǔ)了隨機(jī)雷數(shù)組已用大小和總空間,然后生成一個(gè)數(shù)組,并將各個(gè)雷進(jìn)行編號(hào)存入數(shù)組中。之后rand函數(shù)產(chǎn)生的隨機(jī)雷就在這些數(shù)組中產(chǎn)生。接下來驗(yàn)證我們的想法:
跟進(jìn)下一個(gè)call,發(fā)現(xiàn)這里申請(qǐng)數(shù)組空間,并存儲(chǔ)隨機(jī)出來的雷值
繼續(xù)單步,發(fā)現(xiàn)有個(gè)小循環(huán)比較有意思
看一下rax存儲(chǔ)了什么?
這好像是存儲(chǔ)了多個(gè)數(shù)組的首地址啊,正好我們現(xiàn)在設(shè)定了9x9的雷區(qū),這里正好9個(gè)地址,我們?cè)俑M(jìn)去這些地址看一下
這里+10處又存儲(chǔ)了一個(gè)地址,繼續(xù)觀察,發(fā)現(xiàn)有個(gè)byte數(shù)組,存儲(chǔ)了雷的狀態(tài),有雷就是1,無雷就是0
這個(gè)時(shí)候我們就基本搞明白了這個(gè)Win7下掃雷是怎么布置的了。可是問題來了,最初的記錄雷區(qū)各個(gè)數(shù)組的地址從哪得啊?我們逆著代碼去溯源。我們發(fā)現(xiàn)這個(gè)值是rax+0x10處的存儲(chǔ)的值,而rax是rsi+0x58處存儲(chǔ)的值,這個(gè)rsi是rcx作為上層調(diào)用函數(shù)傳過來的參,我們走出這個(gè)函數(shù)看看這個(gè)參數(shù)從哪里得到。
我們找到了這樣一個(gè)值,在FFCFAA38中存儲(chǔ)了我們所想要的rsi的值。我們知道,這是一個(gè)全局變量,存儲(chǔ)了rsi地址。但是這個(gè)值由于RSLR機(jī)制而導(dǎo)致每次地址不一樣。我們有一種方法得到這個(gè)值,我們先看當(dāng)前模塊加載基地址,然后用FFCFAA38(全局變量地址)-FFC500000(當(dāng)前模塊加載地址)= AAA38(相對(duì)當(dāng)前模塊偏移)。這樣我們可以用GetModuleHandle函數(shù)得到當(dāng)前模塊加載基地址,然后加上這個(gè)偏移AAA38就得到了全局變量地址。
這樣我們就有了得到數(shù)組地址的方法:
Address = [[[[hModule+0xAAA38]+0x18]+0x58]+0x10]
這時(shí)Address就是存儲(chǔ)雷區(qū)數(shù)組的首地址,每個(gè)雷區(qū)地址+0x10處就是雷區(qū)列狀態(tài)數(shù)組(byte)地址。
其實(shí),到這里我們也開始明白了,它所使用的應(yīng)該是C++的vector,一個(gè)個(gè)push才產(chǎn)生這樣的內(nèi)存空間的,不得不說,這C++功力已經(jīng)爐火存青了,各種數(shù)據(jù)結(jié)構(gòu)弄得頭都大了。
找到了雷區(qū)布置數(shù)組就可以進(jìn)行下一步動(dòng)作了,我們通過計(jì)算鼠標(biāo)坐標(biāo)值來獲得雷區(qū)格子,每個(gè)格子是17*17像素大小并加上1像素的邊,所以每個(gè)格子大小為18像素,雷區(qū)邊界為30像素。你問我這些怎么得到的?這些值肯定在某個(gè)內(nèi)存存著,你可以下斷點(diǎn)在GetCursorPos處,在你移動(dòng)鼠標(biāo)時(shí)會(huì)觸發(fā)斷點(diǎn),然后跳出函數(shù),發(fā)現(xiàn)下邊有一個(gè)GetWindowRect函數(shù),這個(gè)函數(shù)會(huì)傳遞窗口句柄,窗口句柄存儲(chǔ)在一個(gè)全局內(nèi)存中,我們可以得到這個(gè)窗口句柄。但是我用了更簡(jiǎn)單的方法,既然有窗口,我直接用工具測(cè)一下就知道每個(gè)格子大小了么。
這樣我們就得到了鼠標(biāo)坐標(biāo)轉(zhuǎn)換格子的公式:
intx = (xPos - 30) / 18;//列
inty = (yPos - 30) / 18;//行
最后,上輔助代碼:
WNDPROC g_oldProc = NULL;
DWORD dwArrOffset = 0xAAA38; //這是重定向之前全局變量相對(duì)于模塊基地址的位置
DWORD64 dwMineAddress = 0; //雷區(qū)列數(shù)組位置
BYTE *pMineMap; //自定義一個(gè)數(shù)組存儲(chǔ)雷的布局,便于訪問
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);
//發(fā)送鼠標(biāo)點(diǎn)擊消息
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"已加載!
");
//獲得雷區(qū)數(shù)組地址
HMODULE hModule = GetModuleHandle(NULL);
// 雷區(qū)列數(shù)組獲得公式 [[[[hModule+0xAAA38]+0x18]+0x58]+0x10]
DWORD64 v1 = *(DWORD64*)((DWORD64)hModule + dwArrOffset);
//v2+0x8處存儲(chǔ)了雷總數(shù)(DWORD),v2+0x0C處存儲(chǔ)了行總數(shù)(DWORD),v2+0x10處存儲(chǔ)了列總數(shù)(DWORD)
DWORD64 v2 = *(DWORD64*)(v1 + 0x18);
//申請(qǐng)一個(gè)存儲(chǔ)雷區(qū)的數(shù)組空間
rows = *(DWORD*)(v2+0x0C); //行
cols = *(DWORD*)(v2+0x10); //列
pMineMap = (BYTE*)VirtualAlloc(NULL,rows*cols, MEM_COMMIT, PAGE_READWRITE);
if (pMineMap ==NULL)
{
OutputDebugString(L"申請(qǐng)內(nèi)存失敗!
");
}
OutputDebugString(L"申請(qǐng)內(nèi)存成功!
");
//v1處存儲(chǔ)了列總數(shù)(DWORD)
v1 = *(DWORD64*)(v2 + 0x58);
dwMineAddress = *(DWORD64*)(v1 + 0x10);//存儲(chǔ)雷區(qū)列數(shù)組地址,有多少列就有多少數(shù)組
//數(shù)組地址首位顯示的是行數(shù)
for (int i=0;i<*((DWORD*)(v2+0x10));i++)
{
//v1處前4字節(jié)(DWORD)儲(chǔ)了行總數(shù) ,后邊兩個(gè)內(nèi)容沒搞懂(10 10)
v1 = *(DWORD64*)(dwMineAddress + i * 8);
BYTE *v5 = (BYTE*)(*(DWORD64*)(v1 + 0x10));
for (int j = 0; j < *((DWORD*)(v2 + 0x0C)); j++)
{
//將雷區(qū)狀態(tài)賦值到數(shù)組中去
pMineMap[i*rows +j] = *v5; //第i列第j行
v5++;
}
}
OutputDebugString(L"雷區(qū)賦值成功!
");
HWND hWnd = FindWindow(NULL, L"掃雷");
if (hWnd == NULL)
{
OutputDebugString(L"未找到目標(biāo)窗口!
");
return FALSE;
}
//更改指定窗口的屬性
//返回值是之前的窗口函數(shù)
g_oldProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WindowProc);
return TRUE;
}
到此,我們大功告成,需要注意的是每次要點(diǎn)擊一下一個(gè)格子再注入動(dòng)態(tài)庫(kù)。不過,我的F12一鍵掃雷并沒成功有人知道是怎么回事么?希望不吝賜教幫我解決一下,嘻嘻~~
總結(jié)
以上是生活随笔為你收集整理的Win7下64位扫雷逆向以及辅助制作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 老笔记本电脑如何升级电脑如何升级
- 下一篇: 学习信息学奥赛,这五大的网站别忘了收藏