閨蜜的電腦是新買的臺(tái)式的,看著它我就想起了在學(xué)校上電腦課,偷偷玩蜘蛛紙牌和掃雷的事情。還記得當(dāng)時(shí)玩掃雷的時(shí)候心情總是非常的緊張,倒不是因?yàn)楹ε吕蠋煱l(fā)現(xiàn),而是害怕自己突然被雷"炸死"。
然而當(dāng)我打開電腦的時(shí)候,我并沒(méi)有找到掃雷這款游戲,也沒(méi)有辦法下載,我問(wèn)我閨蜜,她說(shuō)也表示不知道原因,所以最后我就直接上西瓜視頻尋找答案了。
最后我終于在西瓜視頻創(chuàng)作人"GM的秘密基地"制作的視頻"掃雷為什么在win10上消失了?原因竟然和國(guó)際反地組織相關(guān)!"里面,找到了我想要的答案。
1. 為減少麻煩,最初微軟將掃雷換成了"花田"
上世紀(jì)末,在國(guó)際上興起了一股反地雷風(fēng)潮,參加的國(guó)家高達(dá)100多個(gè)。這100個(gè)國(guó)家還簽署了一份公約,并呼吁全球禁止生產(chǎn)和使用地雷。這本來(lái)是一件好事,因?yàn)榈乩椎耐{的確很大,現(xiàn)在我國(guó)都有戰(zhàn)士因?yàn)閽呃锥粩酄奚?/p>
但是萬(wàn)萬(wàn)沒(méi)有想到的是,一位意大利網(wǎng)友竟然趁著這次機(jī)會(huì)開始針對(duì)微軟的掃雷游戲,并且還發(fā)起了一場(chǎng)"國(guó)際禁止掃雷運(yùn)動(dòng)",受到了很多人的鼓舞與支持,而這場(chǎng)運(yùn)動(dòng)簡(jiǎn)稱ICBW。
他們要求微軟刪除掉系統(tǒng)里的掃雷游戲,因?yàn)閽呃讉Φ搅嗣爸kU(xiǎn)排雷的戰(zhàn)士,以及廣大的地雷受害者,這么聽來(lái),好像也沒(méi)有什么毛病。而當(dāng)時(shí)微軟也為了避免麻煩和影響的進(jìn)一步擴(kuò)散,就很快修改了意大利版本的windows,把里面的掃雷換皮成了一個(gè)叫"花田"的游戲。
2. 網(wǎng)友不買賬,微軟迫不得已只有刪除
微軟以為這樣做事情就可以告一段落了,但是后來(lái)證明并沒(méi)有。因?yàn)槟俏灰獯罄W(wǎng)友看到微軟的處理結(jié)果之后,又帶領(lǐng)著ICBW開始抗議。
于是,微軟百般無(wú)奈之下,只有在后來(lái)的vista和win7中把所有版本的掃雷全部換成了"花田"。到了win8開始甚至都不再自帶掃雷,只能靠主動(dòng)去搜索來(lái)玩這款經(jīng)典的游戲了。
雖然沒(méi)有了這些游戲讓人覺(jué)得很難過(guò),但是我們還可以從另一角度來(lái)看待這個(gè)問(wèn)題,那就是微軟發(fā)明這些自帶的小游戲,其實(shí)完全是為了讓用戶熟悉鼠標(biāo)的用法,而現(xiàn)在隨著電腦的普及,人們?cè)缇蛯W(xué)會(huì)了如何使用鼠標(biāo),所以自然也到了它們退出歷史舞臺(tái)的時(shí)候了。
關(guān)于"win10為什么沒(méi)有掃雷"的科普就到這里了,但是西瓜視頻上的科普一直都在增加,所以趕快上西瓜視頻搜索:GM的秘密基地,然后觀看視頻漲知識(shí)吧!
前逆過(guò)XP下的掃雷程序,感覺(jué)XP下的掃雷很簡(jiǎn)單,但是發(fā)現(xiàn)網(wǎng)上對(duì)于Win7下的掃雷逆向很少很少,于是就試著繼續(xù)逆一下Win7下的掃雷。這一逆發(fā)現(xiàn)難度提升了不只一個(gè)等級(jí)啊,經(jīng)過(guò)兩天的努力,終于整個(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)過(guò)多次循環(huán)后,發(fā)現(xiàn)數(shù)組填充完畢,之后觀察一下申請(qǐng)的數(shù)組空間中存儲(chǔ)的東西
發(fā)現(xiàn)沒(méi)有了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)生。接下來(lái)驗(yàn)證我們的想法:
跟進(jìn)下一個(gè)call,發(fā)現(xiàn)這里申請(qǐng)數(shù)組空間,并存儲(chǔ)隨機(jī)出來(lái)的雷值
繼續(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,無(wú)雷就是0
這個(gè)時(shí)候我們就基本搞明白了這個(gè)Win7下掃雷是怎么布置的了。可是問(wèn)題來(lái)了,最初的記錄雷區(qū)各個(gè)數(shù)組的地址從哪得啊?我們逆著代碼去溯源。我們發(fā)現(xiàn)這個(gè)值是rax+0x10處的存儲(chǔ)的值,而rax是rsi+0x58處存儲(chǔ)的值,這個(gè)rsi是rcx作為上層調(diào)用函數(shù)傳過(guò)來(lái)的參,我們走出這個(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)存空間的,不得不說(shuō),這C++功力已經(jīng)爐火存青了,各種數(shù)據(jù)結(jié)構(gòu)弄得頭都大了。
找到了雷區(qū)布置數(shù)組就可以進(jìn)行下一步動(dòng)作了,我們通過(guò)計(jì)算鼠標(biāo)坐標(biāo)值來(lái)獲得雷區(qū)格子,每個(gè)格子是17*17像素大小并加上1像素的邊,所以每個(gè)格子大小為18像素,雷區(qū)邊界為30像素。你問(wèn)我這些怎么得到的?這些值肯定在某個(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)換格子的公式:
int x=(xPos - 30) / 18; //列
int y=(yPos - 30) / 18; //行
最后,上輔助代碼:
WNDPROC g_oldProc=NULL;
DWORD
dwArrOffset=0xAAA38;
//這是重定向之前全局變量相對(duì)于模塊基地址的位置
DWORD64
dwMineAddress=0;
//雷區(qū)列數(shù)組位置
BYTE
*pMineMap;
//自定義一個(gè)數(shù)組存儲(chǔ)雷的布局,便于訪問(wèn)
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
"已加載!\n"
);
//獲得雷區(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)存失敗!\n"
);
}
OutputDebugString(L
"申請(qǐng)內(nèi)存成功!\n"
);
//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)容沒(mé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ū)賦值成功!\n"
);
HWND
hWnd=FindWindow(NULL, L
"掃雷"
);
if
(hWnd==NULL)
{
OutputDebugString(L
"未找到目標(biāo)窗口!\n"
);
return
FALSE;
}
//更改指定窗口的屬性
//返回值是之前的窗口函數(shù)
g_oldProc=(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (
LONG_PTR
)WindowProc);
return
TRUE;
}
到此,我們大功告成,需要注意的是每次要點(diǎn)擊一下一個(gè)格子再注入動(dòng)態(tài)庫(kù)。不過(guò),我的F12一鍵掃雷并沒(méi)成功有人知道是怎么回事么?希望不吝賜教幫我解決一下,嘻嘻~~
作者:ixiaohuo ,原文發(fā)表于看雪論壇 —【軟件逆向】版塊
責(zé)編:王冬奇
中關(guān)村在線消息:在全新的Windows 10操作系統(tǒng)中,諸如開始菜單等經(jīng)典功能部分回歸。而國(guó)外有消息透露,非常經(jīng)典的《紙牌(Solitaire)》游戲也將正式回歸Windows操作系統(tǒng),讓不少網(wǎng)友驚呼《掃雷(Minesweeper)》還會(huì)遠(yuǎn)嗎?
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
報(bào)道稱,目前《紙牌》游戲的細(xì)節(jié)仍然稀缺,暫時(shí)還沒(méi)有任何證實(shí)消息的截圖。不過(guò),有使用Windows 8/8.1操作系統(tǒng)的用戶在Windows Store應(yīng)用程序商店內(nèi)找到了微軟官方出品的《微軟紙牌游戲合集(MicrosoftSolitaire Collection)》。
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
在APP介紹中,微軟也表示《微軟紙牌游戲合集》始終保持著最受歡迎電腦游戲的桂冠,并且伴隨Windows系統(tǒng)已20年有余。該合集將5種不同的紙牌游戲玩法集合在了起來(lái),用戶還可將紙牌游戲的分?jǐn)?shù)與好友一較高下。
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
現(xiàn)在,這個(gè)《微軟紙牌游戲合集》應(yīng)用程序?qū)⒅匦履J(rèn)預(yù)裝到Windows 10上,并融入了新Windows更清晰的現(xiàn)代用戶界面設(shè)計(jì)風(fēng)格,還支持通過(guò)觸屏操作玩游戲。
《紙牌》游戲?qū)⒄交貧wWindows 10(圖片來(lái)自neowin)
在Windows 10 build 10056截圖中也可以看到,《微軟紙牌游戲合集》已經(jīng)回到Windows上,但名稱中仍印有“Preview”,還處于早期測(cè)試當(dāng)中。所以,目前商店里的“紙牌”游戲版本可能與最終版本又有所不同。