—— besterChen
其實(shí)說實(shí)話,我寫這類文章有些牽強(qiáng),因?yàn)槲乙彩切率?。剛學(xué)習(xí)這個技術(shù)所以我所了解的東西實(shí)在是太少,沒有什么太多的東西分享給大家,只希望寫這點(diǎn)東西能幫助到那些跟我一樣或者比我還菜的新手朋友。
做外掛,貌似是很多高手所不齒的一個偏行,但似乎又是在現(xiàn)在這個游戲橫行年代中很流行的技術(shù)話題,但若是我們仔細(xì)探究其根源:做外掛似乎也沒什么太高的技術(shù)含量,說到底就是編程。
今天,就由我來帶領(lǐng)大家了解一下做外掛這個“技術(shù)”的方方面面吧。
一、 引子
由于我在外掛方面也是一個剛?cè)腴T的新人,這里所講的也僅僅是我所接觸層面上的一些知識,肯定是不全面的,因此,我在這里就盡量把我所了解到的知識講的盡可能詳細(xì),以彌補(bǔ)我知識面上的欠缺,希望大家能夠諒解,也希望大家莫要笑話我……
本文,我將從基礎(chǔ)的數(shù)據(jù)分析開始著手,到程序?qū)崿F(xiàn),再到過保護(hù),再到節(jié)省系統(tǒng)資源等,以這樣層層遞進(jìn)的方式來講述我要展現(xiàn)給大家的東西。
在每個知識點(diǎn)講解的時候,我會盡量的分好每一個小節(jié),爭取讓它更有條理,希望這樣講述大家能夠接受也希望大家在技術(shù)上有所突破。
在正式開始我們的旅程之前,希望大家能夠提前了解下如下一些知識:
1、 OD的使用方法。
2、 了解些內(nèi)存搜索工具的用法。
3、 掌握些基礎(chǔ)的編程思想
希望大家在思考所有問題的時侯,都能從編程的角度去考慮,因?yàn)檫@樣就可以有更多的思路、更多的分析方法,更多的技巧,這些是那些不懂編程的朋友永遠(yuǎn)得不到的強(qiáng)處。
二、 關(guān)于數(shù)據(jù)分析
在網(wǎng)上,尤其像廣海,斷點(diǎn)這樣的外掛論壇,上面有很多關(guān)于數(shù)據(jù)分析的教程,其中很多文章講的就是用搜索工具查找數(shù)據(jù)指針,而且似乎很多人都不厭其煩的在講,在寫,在討論,但是當(dāng)我仔細(xì)去問究為什么的時候,似乎沒幾個人能給一個令我滿意的答案。
相信看這個文章的讀者肯定看過很多像我剛才說的那樣的文章了,我也相信很多的朋友都會也知道為什么用搜索工具搜數(shù)據(jù)指針。當(dāng)然也有很多的朋友不知道的,這里我就從最基礎(chǔ)的開始說。
1、基礎(chǔ)屬性數(shù)據(jù)的分析
A. 需求分析
當(dāng)我們在做掛的時候,我們需要根據(jù)角色的血量,魔法量的多少來吃藥,以保證角色不死掉來提高掛機(jī)的效率。在掛機(jī)的時候,怕角色亂跑,所以我們需要根據(jù)坐標(biāo)來定一個掛機(jī)范圍,因此,我們需要分析角色的基礎(chǔ)屬性。
B. 起始思路
找基礎(chǔ)屬性,有這么幾個方法,思路是這樣的:
i. 想很多教程中寫的,根據(jù)血量、等級、經(jīng)驗(yàn)、攻擊等任何一個屬性值的變化,來搜索變化的值,然后用CE找數(shù)據(jù)指針。
ii. 從編程角度來考慮,任何一個在屏幕上顯示的數(shù)據(jù)都是字符,游戲中顯示的屬性信息都是字符,比如血量在游戲的數(shù)據(jù)結(jié)構(gòu)中應(yīng)該是整型數(shù)據(jù),在顯示到屏幕上需要進(jìn)行類型轉(zhuǎn)換,調(diào)用類似sprintf這樣的函數(shù),比如:
004E8E01 |. 8B86 B0020000 mov eax, dword ptr [esi+0x2B0] ; 取出當(dāng)前血量
004E8E07 |. 8B8E 78020000 mov ecx, dword ptr [esi+0x278] ; 取出最大血量
004E8E0D |. 50 push eax
004E8E0E |. 51 push ecx
004E8E0F |. 8D5424 58 lea edx, dword ptr [esp+0x58]
004E8E13 |. 68 88BE9C00 push 009CBE88 ; UNICODE "%d / %d"
004E8E18 |. 52 push edx
004E8E19 |. FFD5 call ebp ; sprintf函數(shù)
這樣,我們就可以通過在OD中搜索相應(yīng)的字符串來定位到如上這樣的代碼,在仔細(xì)分析ESI的來歷,這樣我們就可以得到相應(yīng)數(shù)據(jù)結(jié)構(gòu)的指針。
C. 調(diào)試過程
這里我舉一個例子,就像我前些日子分析的口袋西游。
內(nèi)存搜索,找力量值,得到結(jié)果如下:
106CC99C 6
內(nèi)存訪問斷點(diǎn),來到如下代碼:
004E8F01 |. 8B86 18030000 mov eax, dword ptr [esi+0x318] ; 最大力量
004E8F07 |. 8B8E 14030000 mov ecx, dword ptr [esi+0x314] ; 最小力量
004E8F0D |. 50 push eax
004E8F0E |. 51 push ecx
004E8F0F |. 8D5424 58 lea edx, dword ptr [esp+0x58]
004E8F13 |. 68 24969C00 push 009C9624 ; UNICODE "%d-%d"
004E8F18 |. 52 push edx
004E8F19 |. FFD5 call ebp ; 顯示出來
這些代碼由此向下,有很多的數(shù)據(jù)都是這個結(jié)構(gòu)體的成員,具體的含義大家可以跟一下,對比游戲中顯示的數(shù)據(jù)猜出其具體含義。
004E8B19 |. E8 3249FEFF call 004CD450
{
004CD450 /$ A1 4C28A200 mov eax, dword ptr [0xA2284C]
004CD455 |. 8B48 1C mov ecx, dword ptr [eax+0x1C]
004CD458 |. 8B41 28 mov eax, dword ptr [ecx+0x28]
004CD45B \. C3 retn
}
004E8B1E |. 8BF0 mov esi, eax
下面總結(jié)一下:
0xA2284C]+0x1C]+0x28]
+3C X
+40 Z
+44 Y
+264: 角色ID
+26C: 角色職業(yè) 1:靈劍,2:日羽,3:槍俠,4:薩滿,5:法皇,6:藥王
+270: 角色等級
+278: 當(dāng)前血量
+27C: 當(dāng)前魔法
+280: 當(dāng)前異能
+284: 當(dāng)前精力
+2B0: 最大血量
+2B4: 最大魔法
+2B8: 最大異能
+2BC: 最大精力
+348: 背包金錢
+434: 名字
+704: 移動速度
+8F4: 選擇的目標(biāo)ID
這樣我們的角色基礎(chǔ)屬性就算是分析完成了,可以根據(jù)編程需要將它整理成對應(yīng)的結(jié)構(gòu)體。比如:
// 個人屬性信息結(jié)構(gòu)
typedef struct _GAME_CHAR_INFO
{
DWORD UnKnown1[15]; // 未知 offset 0
float fX; // X坐標(biāo) offset 0x3C
float fZ; // Z坐標(biāo) offset 0x40
float fY; // Y坐標(biāo) offset 0x44
DWORD UnKnown2[135]; // 未知 offset 0x48
DWORD dwSID; // 角色ID offset 0x264
DWORD UnKnown3; // 未知 offset 0x268
DWORD dwZhiYe; // 職業(yè) offset 0x26C 1:靈劍,2:日羽,3:槍俠,4:薩滿,5:法皇,6:藥王
DWORD dwLv; // 等級 offset 0x270
DWORD UnKnown4; // 未知 offset 0x274
DWORD dwCurHP; // 當(dāng)前HP offset 0x278
DWORD dwCurMP; // 當(dāng)前MP offset 0x27C
DWORD dwCurYN; //當(dāng)前異能 offset 0x280
DWORD dwCurJL; //當(dāng)前精力 offset 0x284
DWORD UnKnown5[10]; // 未知 offset 0x28C
DWORD dwMaxHP; // 最大HP offset 0x2B0
DWORD dwMaxMP; // 最大MP offset 0x2B4
DWORD dwMaxYN; //最大異能 offset 0x2B8
DWORD dwMaxJL; //最大精力 offset 0x2BC
DWORD UnKnown6[34]; // 未知 offset 0x2C0
DWORD dwMoney; // 金錢 offset 0x348
DWORD UnKnown7[58]; // 未知 offset 0x34C
wchar_t* strName; //人物名稱 offset 0x434
DWORD UnKnown8[0x12F]; // 未知 offset 0x438
DWORD dwTSID; // 目標(biāo)實(shí)例offset 0x8F4
DWORD UnKnown9[0x2B]; // 未知 offset 0x8F8
PGAME_BAG_FIRST pGBF; // 背包結(jié)構(gòu)指針offset 0x9A4
PGAME_BAG_FIRST pGBF; // 裝備結(jié)構(gòu)指針offset 0x9A8
DWORD UnKnown10[0x28]; // 未知 offset 0x9AC
PGAME_SKILL_LIST pGSL; // 技能列表 offset 0xA4C
DWORD nCurSkillNum; // 當(dāng)前技能數(shù)量offset 0xA50(基礎(chǔ)是x17,學(xué)一個技能加)
DWORD nMaxSkillNum; // 最大技能數(shù)量offset 0xA54
} GAME_CHAR_INFO, *PGAME_CHAR_INFO;
到這里,無論是從分析的角度還是從編程的角度,這個數(shù)據(jù)的分析就算是完工了。下面我們來看一下怎么用代碼來操作這些數(shù)據(jù)。
D. 代碼實(shí)現(xiàn)
關(guān)于讀取內(nèi)存的代碼幾乎不同的人都有不同的寫法,當(dāng)然各有利弊,我這里寫我比較習(xí)慣的方法吧。
關(guān)于定位數(shù)據(jù)結(jié)構(gòu)的首地址:
/************************************************************************/
/* 獲取人物信息指針 */
/* [g_dwBasePointAddr]+0x1C]+0x28] */
/************************************************************************/
__declspec(naked) PGAME_CHAR_INFO WINAPI GetCharInfoPoint()
{
__asm
{
mov eax, dword ptr [g_dwBasePointAddr]
test eax, eax
je NULL_POINT
mov eax, dword ptr [eax]
test eax, eax
je NULL_POINT
mov eax, dword ptr [eax+0x1C]
test eax, eax
je NULL_POINT
mov eax, dword ptr [eax+0x28]
NULL_POINT:
retn
}
}
這樣,只要我們調(diào)用這個函數(shù)就可以定位到PGAME_CHAR_INFO結(jié)構(gòu),關(guān)于這個結(jié)構(gòu)的定義和分析參考本數(shù)據(jù)的分析部分。
具體的使用,我給出我的MFC的顯示函數(shù):
/************************************************************************/
/* 功能:顯示人物信息 */
/* 參數(shù):無 */
/* 返回:無 */
/************************************************************************/
void CFirstTask::ShowRoleInfo()
{
wchar_t szTemp[512] = {0};
PGAME_CHAR_INFO pGCI = GetCharInfoPoint();
SetDlgItemTextW(IDC_MYNAME,pGCI->strName);
switch (pGCI->dwZhiYe)
{
case 1:
lstrcpyW(szTemp, L"靈劍\0");
break;
case 2:
lstrcpyW(szTemp, L"日羽\0");
break;
case 3:
lstrcpyW(szTemp, L"槍俠\0");
break;
case 4:
lstrcpyW(szTemp, L"薩滿\0");
break;
case 5:
lstrcpyW(szTemp, L"法皇\0");
break;
case 6:
lstrcpyW(szTemp, L"藥王\0");
break;
default:
lstrcpyW(szTemp, L"未知\0");
break;
}
SetDlgItemTextW(IDC_MYZHIYE, szTemp);
wsprintfW(szTemp, L"%d/%d", pGCI->dwCurHP,pGCI->dwMaxHP);
SetDlgItemTextW(IDC_MYHP, szTemp);
wsprintfW(szTemp, L"%d/%d", pGCI->dwCurMP,pGCI->dwMaxMP);
SetDlgItemTextW(IDC_MYMP, szTemp);
wsprintfW(szTemp, L"%d/%d", pGCI->dwCurYN,pGCI->dwMaxYN);
SetDlgItemTextW(IDC_MYYN, szTemp);
wsprintfW(szTemp, L"%d/%d", pGCI->dwCurJL,pGCI->dwMaxJL);
SetDlgItemTextW(IDC_MYJL, szTemp);
swprintf(szTemp, L"%.0f,%.0f,%.0f↑", pGCI->fX, pGCI->fY, pGCI->fZ);
SetDlgItemTextW(IDC_MYXYZ, szTemp);
wsprintfW(szTemp, _T("%d金%d銀%d銅"),
pGCI->dwMoney / 10000, (pGCI->dwMoney % 10000) / 100, (pGCI->dwMoney % 10000) % 100);
SetDlgItemTextW(IDC_MYMONEY, szTemp);
wsprintfW(szTemp, L"%d", pGCI->dwLv);
SetDlgItemTextW(IDC_MYLEVEL, szTemp);
}
貼一下顯示的效果:
