掀開高階遊戲黑客的面紗,教你打造遊戲修改器 (10千字)

看雪資料發表於2015-11-15

掀開高階遊戲黑客的面紗,教你打造遊戲修改器
工具:SoftICE、金山遊俠2002、VC++7.0、PE檢視器、SPY++
測試平臺:Window2000 Professional SP2

大家好!我先給大家拜個晚年,時間過真快工,一年又過去了,我也和大家分開一年了,真是感慨萬分呀,不知道大還記得不記得YY了,不過我是不會忘記大家的.
這一年真是太忙了,根本就沒有時間再像從前那樣在深夜裡一邊聽著音樂一邊寫文章了,今天就著假期,再給大家充充電:D
  今天YY給大家帶來些什麼呢?呵呵,看題目就知道了,看起來很酷吧,“高階遊戲黑客”,什麼?你說你就遊戲黑客?看好了,是“高階”的!什麼是高階的?等你看完這篇文章就知道了:D
首先我介紹一下將會用到的工具:
1、    SoftICE(不用多說了吧,我想你應該會用)
2、    金山遊俠2002(這個你也應該會用)
3、    VC++7.0(不要求你一定會用,但至少應該會一種程式設計工具)
4、    PE檢視器(你可以隨意找一個,沒有也沒關係,我會教你用SoftICE檢視)
5、    SPY++(VC裡的一個檢視程式資訊的工具,你可以和別的,比如Delphi和C++Builder的WinSight32)

然後就是你應該會的知識:
1、    彙編基礎
2、    一些程式設計基礎,至少應該看懂我介紹的幾個API函式
3、    PE檔案結構的基礎,不會也沒關係,我會解釋給你

以上幾點你都具備了的話我們就可以開始了。

我來介紹一下我要教給你的東西。想必大家都玩過PC遊吧,那麼也一定用過一些專用的遊戲修改器吧,比如暗黑,紅警,大富翁這些經典的遊戲都有它們專用的修改器,注意,我說的不是FPE之類的通用修改工具。
你試沒試過用金山遊俠修改紅警二的金錢?如果有的話你應該知道每玩一次就要改一次,因為這個遊戲是動態分配記憶體的,每次重新開始都會改變。所以你會選擇到網上去下載一個專用的修改器,那麼你有沒有想過自己做一上呢?想過?那你為什麼不做?什麼不會?那就好辦了,看了這篇教程你就會了:D費話少說,我來講一下原理。
有一些經常修改遊的朋友一定會知道,不論遊戲中“物品”的記憶體地址是否是動態的,物品與物品之間相隔的距離都是不變的,我拿“楚留香新傳”為例,我先用金山遊俠查詢內力值的記憶體地址,找到的結果是:79F695C,再查詢物品“金創藥”的地址是:328D1DC,現在我用79F695C減去328D1DC,得到:4769780,這個數就是內力值與金創藥的偏移值,沒看懂?接著看呀,我還沒說完呢,現在重新再執行遊戲,查詢內力值的地址,得到:798695C再查詢金創藥得到的地址是:321D1DC,兩個值的記憶體地址都改變了,但是用你內力值的地址減去金創藥的地址得到的結果是什麼?沒錯,還是4769780,也就是說,無論這兩個值的記憶體地址變成多少,它們之間的距離是永遠不變的,不光是這個遊戲,一般的遊戲都是,至少我沒見過不是的:D
上面講的東西總結出一個結論,那就是我們只要得到這兩個地址中的任何一個,就可以得到另外一個,只要你知道它們之間的偏移量是多少。
我們第一步要做的就是得到這個地址,但是記憶體中的地址是動態改變的,得到也沒有用,這裡我就教你把它變成靜態的,叫它永遠都不變!我繼續拿“楚留香新傳”為例,如果你有這個遊的話就跟我一起做,沒有的也沒關係,只要看懂這幾個步驟就行了。開工!
首先進入遊戲,查詢內值的地址,得到的是:798695C(不知道為什麼這上游並不是每次重起都改變記憶體地址),按Ctrl+D開啟SoftICE,下命令:BPM 798695C W(寫這個地址時則中斷),回到遊戲中,開啟人物屬性皮膚,遊戲中斷了,在SofitICE中你會看到這條指令:

0047EB17  MOV  EAX [EDX+000003F4] 下命令:D EDX+3F4將看到內力值
0047EB1D  PUSH  EAX
………………………………
………………………………
從上面可看出0047EB17處的指令是將內力值的指標送到EAX暫存器中,這是一個典型的定址方式,設想一下,我們是到了EDX中的基址,那麼無論什麼時候只要用EDX+3F4就可以輕鬆的得到內力值的地址,因為000003F4是一個常量,它是不會改變的,改變的只是EDX中的地址,所以只要有辦法得到EDX中的值就什麼都好辦了,你明白了沒有?如果還是不懂,那麼請再看一遍。現在要做的就是如何得到這個值,下面我教給你如何做:
  我的辦法就是設計一段程式碼,把EDX中的值存放到一個地址中,然後執行這段程式碼,再返回遊戲的原有指令繼續執行,什麼?補丁技術?SMC?隨你怎麼說啦,只要執行正常就一切OK啦:D
  實際操作:
首先在程式中找一段空白處來存放我們設計的程式碼,很簡單,只要懂得一些PE檔案結構的朋友都會知道,一般在EXE檔案的資料段(.data段)的結尾都會有一段緩衝區,我們可以在這段區域中寫任何東西,當然你也可以用“90大法”找一段空白區,但我還是推薦你用我教給你的方法。上同我提到,如果你沒有PE檔案檢視工具我可以教你用SoftICE檢視,而且很簡單,只要一個命令:MAP32 “模組名”,看一下我是怎麼做的你就知道了。
Ctrl+D呼收出SoftICE,然後下命令:MAP32 CrhChs,這時你應該看到EXE各個段的資訊,我們要注意的只是.data段,既然要找的是資料段的結尾,那麼我們就從下一個段開始向上找,如下:
  .data  004FB000
  .rsrc  00507000
.data的下一個段是.rsrc段,它是從00507000開始的,也就是說以00507000為基礎向上一個位元組就是資料段的結尾,我所擇從00506950處開始寫程式碼,說了這麼半天那麼我們的程式碼到底是什麼樣子呢?修改後的指令又是什麼樣的呢?別急,請看下面:
  修改0047EB17後程式碼:
0047EB17  JMP    00506950 //跳到我們的程式碼中去執行 
0047EB1C  NOP  //由於這條指令原來的長度是6位元組,而修改後的長度是5個位元組,所以用一個空指令補上
0047EB1D  PUSH  EAX

  //我們的程式碼:
00506950  MOV  DWORD PTR  EAX,[EDX+00003F4] //恢復我們破壞的指令
00506956  MOV  DWORD PTR  [00506961],EDX //把EDX儲存以00506961中去
0050695C  JMP  0047EB1D //返回原來的指令去執行

把上面的程式碼用SoftICE的A命令寫入,OK!
  現在我們試一下執行的效果,你現在用金山遊俠搜尋一下內力址的地址,什麼又變了?那就地啦,它要是不變我們還用費這麼大勁兒嗎?記下這個地址返回到遊戲中去,Ctrl+D撥出SoftICE,下命令 D *[00506961]+000003F4,在資料視窗看到什麼了?呵呵,沒錯,看到了你剛才記住的那個地址,裡面的數值正是內力的值,試著改一下,回到遊戲中,呵呵,內力值變了吧:D
  講到這裡,我們的工作已經完成了%90,但別高興的太早,後面的%10要遠比前的%90花的時間長,因為我們要用程式設計實現這一切,因為你不能每次都像剛才那樣做一次吧!
  現在我來說一下程式設計的步驟:
首先用FindWindow函式得到視窗控制程式碼,然後用GetWindowThreadID函式從視窗控制程式碼得到這個程式的ID,接著用OpenProcess得到程式的讀寫許可權,最後用WriteProcessMemory和ReadProcessMemory讀寫記憶體,然後。。。。呵呵,你的修改器就做成啦:D
  下面是我抄寫以前寫的修改器源程式片斷,第一部分是動態寫入剛才的程式碼,第二部分是讀取並修改內力值,由於我沒有時間整理和測試,所以不能保證沒有錯誤,如果大家發現有遺漏的話,可以在QQ上給我留言或寫信給我,程式碼如下:
有幾點請大家注意:
1、    寫機器碼時要一個位元組一個位元組的寫
2、    注意要先寫入自己的程式碼,然後再修改遊中的指令(下面的程式碼沒有這樣做,因為不影響,但是你應該注意這個問題)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //動態寫入程式碼
  //0047EB17
#define MY_CODE1  0xE9
#define MY_CODE2  0x34
#define MY_CODE3  0x7E
#define MY_CODE4  0x08
#define MY_CODE5  0x00
#define MY_CODE6  0x90
      //00506950
#define MY2_CODE1  0x8B
#define MY2_CODE2  0x82        //這部分是要寫入的機器碼的常量定義
#define MY2_CODE3  0xF4
#define MY2_CODE4  0x03
#define MY2_CODE5  0x00
#define MY2_CODE6  0x00

#define MY3_CODE1  0x89
#define MY3_CODE2  0x15
#define MY3_CODE3  0x61
#define MY3_CODE4  0x69
#define MY3_CODE5  0x50
#define MY3_CODE6  0x00

#define MY4_CODE1  0xE9 
#define MY4_CODE2  0xBC
#define MY4_CODE3  0x81
#define MY4_CODE4  0xF7
#define MY4_CODE5  0xFF
//-----------------------------------------------------------------------------//
DWORD A1 =MY_CODE1;
    DWORD A2 =MY_CODE2;
    DWORD A3 =MY_CODE3;
    DWORD A4 =MY_CODE4;
    DWORD A5 =MY_CODE5;
    DWORD A6 =MY_CODE6;

    DWORD B1 =MY2_CODE1;
    DWORD B2 =MY2_CODE2;
    DWORD B3 =MY2_CODE3;  //這部分是變數的定義
    DWORD B4 =MY2_CODE4;
    DWORD B5 =MY2_CODE5;
    DWORD B6 =MY2_CODE6;

    DWORD C1 =MY3_CODE1;
    DWORD C2 =MY3_CODE2;
    DWORD C3 =MY3_CODE3;
    DWORD C4 =MY3_CODE4;
    DWORD C5 =MY3_CODE5;
    DWORD C6 =MY3_CODE6;

    DWORD D1 =MY4_CODE1;
    DWORD D2 =MY4_CODE2;
    DWORD D3 =MY4_CODE3;
    DWORD D4 =MY4_CODE4;
    DWORD D5 =MY4_CODE5;
//--------------------------------------------------------------------------//
    HWND hWnd =::FindWindow("CRHClass",NULL); //得到視窗控制程式碼
    if(hWnd ==FALSE)
        MessageBox("遊戲沒有執行!");
    else
    {
      GetWindowThreadProcessId(hWnd,&hProcId); // 從視窗控制程式碼得到程式ID
      HANDLE nOK =OpenProcess(PROCESS_ALL_ACCESS|PROCESS_TERMINATE|PROCESS_VM_OPERATION|PROCESS_VM_READ|
                            PROCESS_VM_WRITE,FALSE,hProcId);  //開啟程式並得到讀與許可權
      if(nOK ==NULL)
          MessageBox("開啟程式時出錯");
      else
      {
                          //0047EB17
          WriteProcessMemory(nOK,(LPVOID)0x0047EB17,&A1,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0047EB18,&A2,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0047EB19,&A3,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0047EB1A,&A4,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0047EB1B,&A5,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0047EB1C,&A6,1,NULL);
                          //00506950
          WriteProcessMemory(nOK,(LPVOID)0x00506950,&B1,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506951,&B2,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506952,&B3,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506953,&B4,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506954,&B5,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506955,&B6,1,NULL);
                          //第二句
          WriteProcessMemory(nOK,(LPVOID)0x00506956,&C1,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506957,&C2,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506958,&C3,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506959,&C4,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0050695A,&C5,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0050695B,&C6,1,NULL);
                          //最後一句
          WriteProcessMemory(nOK,(LPVOID)0x0050695C,&D1,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0050695D,&D2,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0050695E,&D3,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x0050695F,&D4,1,NULL);
          WriteProcessMemory(nOK,(LPVOID)0x00506960,&D5,1,NULL);

          CloseHandle(nOK); //關閉程式控制程式碼
      }
    }
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                    //讀取並修改內力值
DWORD  hProcId;
    HWND hWnd =::FindWindow("CRHClass",NULL);
    if(hWnd ==FALSE)
        MessageBox("No");
    else
    {
      GetWindowThreadProcessId(hWnd,&hProcId);
      HANDLE nOK =OpenProcess(PROCESS_ALL_ACCESS|PROCESS_TERMINATE|PROCESS_VM_OPERATION|PROCESS_VM_READ|
                            PROCESS_VM_WRITE,FALSE,hProcId);
      if(nOK ==NULL)
          MessageBox("ProcNo!");
      else
      {
          DWORD buf1;
          DWORD write;
          BOOL OK=ReadProcessMemory(nOK,(LPCVOID)0x00506961,(LPVOID)&buf1,4,NULL); //讀取我們儲存EDX中的基礎
          if(OK ==TRUE)
          {
            write =buf1+0x000003F4;  //得到內力值的地址
            DWORD Writeed =0x00; //要修改的數值
            BOOL B =WriteProcessMemory(nOK,(LPVOID)write,&Writeed,1,NULL);
            if(B==FALSE)
                MessageBox("WriteNo");
          }
      }
          CloseHandle(nOK);
    }

啊,寫的我手都麻啦,今天就到這裡了,才疏學淺難免會有遺漏,請大家指教,如果我不會或不喜歡用VC的話,你可以在QQ上與我交流,我可以教你如何用Delphi、C++Builder、Win32Asm或VC實同上面的功能。
                                                      (如轉載本篇文章請不要改動內容及作者!)
作者:CrackYY
Email:CoolYY@msn.com
OICQ:20651482

相關文章