天天看點

開發外挂的一些原理

看本文前必須先會 ASM , VC , OD , FPE , HOOK 1  遊戲修改

我一直用的是 FPE2000 ,不習慣用其他的修改器,因為有個功能其他的修改器沒有,而且這個功能相當重 要。

怎樣搜尋就不講了,主要講分析。先看血和魔法,大家會發現一般的遊戲血和魔法都在相臨的位置,為什 麼哪?這個原理很簡單,因為程式設計人員的習慣問題。寫遊戲時會定義一個基本的結構,這個結構包含人物 的一些屬性,例如:

struct _CHAR_ATTR

{

  char Name[30]

  DWORD HP,MAXHP;

  DWORD MP,MAXMP;

  DWORD Exp;

}

這樣一來程式運作後配置設定記憶體是按結構配置設定的,是以直接配置設定一個結構的大小并不考慮結構中的變量,那 麼結構中的變量位址當然是相臨的。是以一般查到 HP 的時候就可以查到 1 個人物的一些基本屬性了。以經 驗來講,人物的動作也在這個結構附近,這裡就要介紹隻有 FPE 有的重要功能了。動作一般是一些數字表 示,當人物做一個砍怪的動作時,會分 N 個細節的動作,一般這些動作都是連續的數字,好比 0 是站立準備 砍怪, 1 是舉起武器, 2 是往下揮武器, 3 是砍到怪, 4 是收起武器,然後就是循環這幾個動作,那我們怎樣 找這個位址哪,首先找到 HP 位址,然後在 FPE 上點 EDIT 頁面,這時 FPE 顯示的資料是點選 edit 時的資料,再 轉回遊戲,不要動也不要掉血,不然就不準了,再轉回 FPE ,按下 F5 (這個就是我說的重要功能),有沒 有看到一些位址的資料已經變了,沒錯這些資料變動的資料就是人物的骨骼動畫資料,如果沒有發現變動 也不要緊,按 PAGE UP 或者 PAGE DOWN ,看看附近的記憶體頁是否有資料在變動,接下來就是分析這些動畫數 據了,我們再回遊戲,然後讓你的人物跑起來,要跑的比較遠不然還沒等轉到 FPE 就停了,跑起來後我們 轉回 FPE ,然後一直按着 F5 ,看資料的變化,主要是尋找 * 循環 * 變動的資料,可能剛開始找這些不會太明 白,不過沒關系,憑知覺,看哪個象就在哪個位址鎖定,鎖定完後再去遊戲跑跑看,如果人物跑的動作不 對的話那就證明你成功了,接下來用同樣的方法找打怪的動作,找到後就可以做光人物加速而遊戲速度不 變的功能了:)  什麼?到現在你不還不知道怎麼做人物加速?那我再費點手力,還看上面 0 、 1 、 2 、 3 、 4 動作,我們可以直接去掉 0 、 1 、 2 兩個動作,在 FPE 裡鎖定值是 3 ,判斷是 =0 ,就是當做 0 動作時直接跳到 3 動作。這樣就直接是砍怪和收武器的動作了作了,如果還嫌慢那就把 4 也去掉, FPE 裡同一位址鎖定值是 0 ,判斷是 3 ,這樣光剩下砍的動作了,做完後回遊戲看看,發現你的任務砍怪時一直在抽筋:)

FPE 裡還有個重要的搜尋就是 '?' ,用這個可以搜尋條狀數值,如果不會的話可以哪一些單機遊戲做實驗。 2  視窗化

2D 遊戲視窗化

主要是修改視窗類型。

// 視窗類型和視窗擴充類型

LONG style,exstyle

// 得到視窗類型

style= GetWindowLong( 視窗句柄 ,GWL_STYLE);

// 加上标題欄

style=style | WS_CAPTION ;

// 這裡就是把遊戲設定成視窗模式了

SetWindowLong( 視窗句柄 ,GWL_STYLE,style);// 修改窗體的 exstyle 屬性

// 對于擴充類型可以改也可以不改,看自己喜好了。

exstyle=GetWindowLong( 視窗句柄 ,GWL_EXSTYLE);

exstyle=exstyle | WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;

// 設定視窗的擴充類型

SetWindowLong( 視窗句柄 ,GWL_EXSTYLE,exstyle);

// 這裡是設定視窗的大小,并且設定置頂的屬性

SetWindowPos( 視窗句柄 ,HWND_NOTOPMOST,0,0,800,600,SWP_SHOWWINDOW);

// 顯示視窗

ShowWindow( 視窗句柄 ,SW_SHOWNORMAL);

// 接下來是修改視窗的消息了,一般遊戲會在改變視窗模式消息檢測是否是全屏模式

// 這個變量用來儲存遊戲視窗原先的消息處理函數

WNDPROC OldMsgProc; 

// 得到原來的消息函數

OldMsgProc=(WNDPROC)GetWindowLong( 視窗句柄 ,GWL_WNDPROC);

// 接下來我們就替換自己的視窗消息函數

if(SetWindowLong( 視窗句柄 ,GWL_WNDPROC,(long)MsgProc)==0)

  return false;

// 視窗消息函數

LRESULT CALLBACK MsgProc(HWND hWnd,UINT msg,WPARAM wparam,LPARAM lparam)

{

// 消息過濾

switch (msg)

{

case WM_ACTIVATEAPP:

case WM_ACTIVATE:

case WM_KILLFOCUS:

case WM_SETFOCUS:

case WM_CLOSE:

return 0;

// 殺掉檢測視窗模式的定時器

case WM_TIMER:

if(wparam== 檢測視窗模式的定時器 ID)

KillTimer(hWnd,wparam); // 殺掉

break;

}

return CallWindowProc(OldProcMsg,hWnd,msg,wparam,lparam);

}

這樣就完成視窗化了,還有一點需要注意的,就是如果遊戲啟用定時器檢測視窗狀态時我們必須把這些定 時器關掉,可以用 spy++ 檢測遊戲視窗用了哪幾個定時器。然後記錄下來定時器的 ID ,在視窗消息 3D 遊戲視窗化

3D 遊戲就比較簡單了,主要是靠的是 DirectX 中的 CreateDevice 函數,當然每個 dx 版本建立都不一樣,但 基本步驟都差不多。隻要修改 D3DPRESENT_PARAMETERS 結構就可以實作了。

我們隻要 hook dx 的 CreateDevice ,在其中把 D3DPRESENT_PARAMETERS.Windowed 屬性改為 true 即可。不過 可能導緻遊戲不能正常運作或者畫面位移、透明等,這些就要參考 dx 的 D3DPRESENT_PARAMETERS 結構另外 的參數了。可以參考任意一個 D3D 教程,裡面都有詳細的解釋。 3  遊戲加速

遊戲加速是利用修改時間函數的傳回值。

利用 hook 修改傳回值應該是很簡單的,我就不講了,但是有些遊戲當你利用 hook 修改後他卻提示你修改函 數被修改而終止遊戲。不過不要怕隻要會彙編沒什麼能難倒的。

下面的函數就是修改了 GetTickCount() 函數的傳回值用 vc 寫的。下面是在 hook 初始化時做的工作。

DWORD dwIdOld1=0;

DWORD *p1=((DWORD*)GetTickCount)+3;

VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,PAGE_READWRITE,&dwIdOld1);

if(m_Speed==1)

{

__asm

{

push eax

mov eax,p1

mov [eax+1],0x17 // 加 1 倍速度

pop eax

}

}

else

{

__asm

{

push eax

mov eax,p1

mov [eax+1],0x16 // 加 2 倍速度

pop eax

}

}

VirtualProtectEx(GetCurrentProcess(),(LPVOID)p1,2,dwIdOld1,&dwIdOld1);

這個我就不多講了因為想要源代碼的可以找我。 4  寫屏

大多數人是利用修改遊戲函數寫屏的,我的方法是 HOOK  dx 寫屏。原理很簡單,遊戲是要通過 Blt 和 BltFast 轉換頁面的,我将字寫到背景頁面就可以了,好處是不 必太麻煩找遊戲輸出函數,而且換個遊戲也一樣能用。而且還能貼個圖檔到遊戲。壞處是如果 dx 版本不同 就要修改代碼了。建議用 MS 的 detours ,友善而且穩定。

// 輸出文字到一個頁面

HRESULT DrawText(LPDIRECTDRAWSURFACE m_pdds,TCHAR* strText,DWORD dwOriginX,DWORD dwOriginY,

 COLORREF crBackground,COLORREF crForeground)

{

    HDC hDC = NULL;

    HRESULT hr;

HFONT hFont=NULL;

    if( m_pdds == NULL || strText == NULL )

        return E_INVALIDARG;     // Make sure this surface is restored.

    if( FAILED( hr = m_pdds->Restore() ) )

        return hr;     if( FAILED( hr = m_pdds->GetDC( &hDC ) ) )

        return hr;     // Set the background and foreground color

    SetBkColor( hDC, crBackground );

    SetTextColor( hDC, crForeground );     if( hFont )

        SelectObject( hDC, hFont );     // Use GDI to draw the text on the surface

    TextOut( hDC, dwOriginX, dwOriginY, strText, strlen(strText) );     if( FAILED( hr = m_pdds->ReleaseDC( hDC ) ) )

        return hr;     return S_OK;

}

// 顯示文本太簡單了,就在 HOOK 的函數裡寫 1 句。

// 老版本的 BltFast  這個是從離屏頁面 Copy 圖檔到背景頁面的函數

//DefHookDApi  是我自己寫的快捷定義 hook 函數不用去管。 detours

DefHookDApi(BltFast,HRESULT,(DWORD x,DWORD y,LPDIRECTDRAWSURFACE lpdds, LPRECT lprc,DWORD  n))

{

// 我們直接把東西 Copy 到離屏頁面

DrawText(lpdds,"BltFast",0,0,RGB(0,0,0),RGB(255,255,0));

HRESULT ret=Real_BltFast(x,y,lpdds,lprc,n);

return ret;

}

// 老版本的 Blt  這個是從背景頁面 Copy 首頁面的函數

DefHookDApi(Blt,HRESULT,(GUID FAR *lpGUID,LPRECT lprc,LPDIRECTDRAWSURFACE lpdds,LPRECT  lprc1,

   DWORD n, LPDDBLTFX n1))

{

// 我們直接把東西 Copy 到背景頁面

DrawText(lpdds,"Blt",0,0,RGB(0,0,0),RGB(255,255,0));

HRESULT ret=Real_Blt(lpGUID,lprc,lpdds,lprc1,n,n1);

return ret;

} 5  分析封包

我是用 OD 直接解密的,不提倡用 wpe 看,看的累,而且看半天看不出來東西。

首先是確定遊戲運作檔案沒有加殼。再看看啟動後的遊戲程序名是否跟你運作的 exe 名一樣,如果不一樣那麼我們先來看他是如何啟動的遊戲程序,用 od 取你運作的 exe 檔案,然後在指令裡面輸入 bp CreateWindowW 和 bp CreateWindowExW, 然後按 F9 運作,點你連接配接的伺服器,這時會中斷下來,然後看他啟動的檔案和參數,先在啟動檔案建立個快捷方式,然後把參數填到快捷方式裡,以後直接運作這個快捷方式即可。

用 od 取實際的遊戲執行檔案,然後在指令行輸入 bp send , bp recv , bp WSASend, bp WSARecv 四個指令,再點 od 的調試 - 》參數,把剛才記錄的參數寫進去。按 CTRL+F2 重新取。按 F9 運作。随便輸入個帳号和密碼點進入,這時會中斷到 send 或者 WSASend ,按 CTRL+F9 即可回到遊戲領域,然後像上看,加密可能就在這上面。首先我們來判斷這個函數的開始,如何判斷函數入口那,就是看 PUSH 語句, od 一般把一個函數用藍線擴起來了,比較容易厘清。看函數入口到調用 send 或 WSASend 前面有沒有 call 語句,如果有我們需要跟進去,如果發現離函數入口很近而且沒有 call 語句那直接按 CTRL+F9 再回上一個領域,照這樣的方法就可以找到加密算法了。其實很簡單。

接下來是 recv 和 WSARecv ,首先講 recv ,中斷這裡後我們在堆棧視窗右鍵點 buffer ,然後選記憶體中顯示。再按 CTRL+F9 ,發現記憶體中有了接收的資料,然後在記憶體的第一個位元組右鍵點讀硬體中斷,繼續按 F9 運作吧,很快會中斷到讀取記憶體的地方,很簡單這裡有可能就是加密的地方,如果看不出來那麼在記憶體第 5 個位元組同樣做讀硬體記憶體中斷,很快就能找到解密地方。

WSARecv 的 buffer 不一樣,他是個緩存指針,如果中斷到這裡我們一樣在堆棧視窗右鍵點 buffer ,然後選記憶體中顯示。這是個結構,前 4 個是 buffer 大小,後面的才是資料,按 CTRL+F9 ,發現接收資料,我們直接在第 5 個地方下讀硬體中斷,以後就跟 recv 一樣了。呵呵一切都不難,隻要會彙編就 ok 。 6  找寫屏函數的方法

我用 si 來分析的,因為 od 我不知道怎麼搜尋記憶體。。。

在遊戲的交談欄裡寫一句話,先不要發送,然後用 si 的 s 指令搜尋你要發的這句話比如話是 ‘ 我要搜尋的記憶體 ’ , s ds:00000000 l ffffffff ' 我要搜尋的記憶體 ', 有可能會找到多個位址,這個跟 fpe 的搜尋差不多,一個一個修改試試,用 “d  位址 ” 指令可以檢視找到的位址,然後光标移動到記憶體頁面上面就可以修改了。找到正确的後就要下記憶體斷點了 ,"bpm  位址  r" 指令就可以了,傳回遊戲把話發出去,這時就會中斷到讀這段記憶體的語句了,一般是 lea  彙編指令,意思是把這個記憶體位址副給積存器,然後 PUSH ,然後就是 call 了,至于如何分析 call 有幾個參數可以看 win32asm 教程。有時候也可能複雜點,就是遊戲把這個記憶體拷貝到另一個記憶體然後再輸出,這樣就是多了一個步驟而已,隻要在拷貝到另一個記憶體的位址下個記憶體中斷即可。找到函數如何利用哪?在你的 hook dll 中可以直接調用這個函數,例如這個寫屏函數有 2 個參數, 1 個是 buf ,第 2 個是 buf 的長度,彙編語句是

mov ebx, ds[????]

push ebx

lea eax , ds[????]

push eax

call ?????? // 用 vc 寫個函數,不用懷疑就是那麼簡單

__declspec(naked) Out(char* buf,int len)

{

  mov ebx, len

  push ebx

  lea eax , buf

  push eax

  call ??????

}

一般我都做利用 recv 或 WSARecv 函數傳回失敗後做這些功能。

DefHookApi(recv,int,(SOCKET s,char *buf,int len,int flags))

{

  int ret=Real_recv(s,buf,len,flags);

  if(ret<1&&strcmp(outbuf,""))

  {

    Out(outbuf,strlen(outbuf));

    ZeroMemory( outbuf, sizeof(outbuf) ); // 記得清空不然會一直發送

  }

}

這樣就 ok 了。

繼續閱讀