如何在NP下讀寫遊戲內存及如何進入NP進程

標 題: 【原創】如何在NP下讀寫遊戲內存及如何進入NP進程
作 者: 墮落天才
時 間: 2007-01-04,13:28
鏈 接:
http://bbs.pediy.com/showthread.php?threadid=37417

******************************************************
*標題:【原創】如何在NP下讀寫遊戲內存及如何進入NP進程 *
*作者:墮落天才                                 *
*日期:2007年1月4號                             *
*版權聲明:請保持文章的完整,轉載請註明出處             *
******************************************************

        在上一篇文章《反NP監視原理》中說到要去掉NP的注入是很容易的事,但是去掉npggNT.des並不是說我們想對遊戲怎麼樣都可以了,NP還掛鉤了很多內核函數,所以很多關鍵系

統函數就算我們在用戶層能用也對遊戲沒有什麼效果。
        如果我們想在不破解NP前提下讀寫遊戲內存該怎麼辦呢,我想辦法至少有兩個
一、用驅動
        在驅動下讀寫遊戲內存是沒問題,但是由於我不懂驅動,所以也沒什麼可說。
二、進入遊戲進程
        在用戶層,如果我們想在不破解NP的前提下讀寫遊戲內存的話,大概就只能進入遊戲進程了。因爲很簡單,我們的程序無法對遊戲使用OpenProcess、ReadProcessMemoery及

WriteProcessMemory這些函數(就算是去掉了NP監視模塊npggNT.des),而NP又不可能限制遊戲自身使用這些函數,所以只要我們能夠進入遊戲進程就能夠讀寫遊戲的內存。怎麼

進入遊戲呢?下面介紹兩種方法:

        1,最簡單的辦法 ―全局消息鉤子(WH_GETMESSAGE)
          看似很複雜的東西原來很簡單就可以實現,大道至易啊。使用消息鉤子進入遊戲進程無疑是最簡單的一種方法,具體編程大概象這樣:一個消息鉤子的DLL,裏面包含一個消

息回調函數(什麼都不用做),讀寫內存過程,跟主程序通訊過程或操作界面過程,當然在DLL_PROCESS_ATTACH要判斷當前的進程是不是遊戲的,是的話就做相應的處理;一個安

裝全局消息鉤子的主程序。大概這樣就可以了。使用全局消息鉤子的好處是簡單易用,但是不足之處是要在遊戲完全啓動(NP當然也啓動啦)後才能進入,如果想在NP啓動前做一

些什麼事的話是不可能的。
        另外也簡單介紹一下防全局鉤子的辦法,Windows是通過調用LoadLibraryExW來向目標進程注入鉤子DLL的,所以只要我們在鉤子安裝前掛鉤了這個函數,全局鉤子就干擾不了

了。

        2,更麻煩的辦法 ― 遠程注入
          知道遠程注入方法和原理的人可能會說“有沒有搞錯,OpenProcess、WriteProcessMemory這些必備函數都不能用,怎麼注入?”,當然啦,NP啓動後是不能幹這些事情,所

以我們要在NP啓動前完成。這樣一來,時機就很重要了。
          遊戲啓動的流程大概是這樣:遊戲Main->GameGuard.des->GameMon.des(NP進程)。這裏的做法是這樣:遊戲Main->GameGuard.des(暫停)->注入DLL->GameGuard.des(繼

續)->GameMon.des。關鍵點就是讓GameGuard.des暫停,有什麼辦法?我想到一個是全局消息鉤子(還是少不了它啊)。要實現大概需要做下面的工作:一個全局消息鉤子DLL,裏面只

要一個消息回調函數(什麼都不用做),DLL_PROCESS_ATTACH下進行當前進程判斷找GameGuard.des,找到的話就向主程序SendMessage;主程序,負責安裝鉤子,接收鉤子DLL發來的

消息,接收到消息就開始查找遊戲進程,向遊戲進程注入內存操作DLL,返回給SendMessage讓GameGuard.des繼續,卸載鉤子(免得它繼續鉤來鉤去);內存操作DLL,負責對遊戲

內存進行操作。
          具體編寫如下(有省略):
////////////////////////////////////////////////GameHook.cpp//////////////////////////////////////////////////////////////////
BOOL IsGameGuard();
//////////////////////////////////
LRESULT CALLBACK GetMsgProc(int nCode,WPARAM wParam,LPARAM lParam)
{
return (CallNextHookEx(m_hHook,nCode,wParam,lParam));//什麼都不需要做
}
///////////////////////////////////////
BOOL WINAPI DllMain(HINSTANCE hInst,DWORD dwReason,LPVOID lp)
{
switch(dwReason){
case DLL_PROCESS_ATTACH:  
        if(IsGameGuard())//判斷當前進程是不是GameGuard.des
          SendMessage(m_hwndRecv,WM_HOOK_IN_GAMEGUARD,NULL,NULL);//向主窗體發送消息,SendMessage是等待接受窗體處理完畢才返回的,
        break;                           //所以進程就暫停在這裏,我們有足夠的時間去做事情
case DLL_PROCESS_DETACH:
        break;
}
return TRUE;
}
///////////////////////////////////
GAMEHOOKAPI BOOL SetGameHook(BOOL fInstall,HWND hwnd)
{
...
}
////////////////////////////////////////
BOOL IsGameGuard()
{
          TCHAR szFileName[256];
          GetModuleFileName(NULL,szFileName,256);
          if(strstr(szFileName,"GameGuard.des")!=NULL){//這樣的判斷嚴格來說是有問題的,但實際操作也夠用了。當然也可以進行更嚴格的判斷,不過麻煩點
            return TRUE;
          }
return FALSE;
}
//////////////////////////////////////////////////////Main////////////////////////////////////////////////////////////////////////
void OnGameGuard(WPARAM wParam,LPARAM lParam)//處理消息鉤子DLL發來的消息就是上面SendMessage的那個
{
DWORD dwProcessId=FindGameProcess(m_strGameName);//開始查找遊戲進程
if(dwProcessId==0){
        MessageBox(m_hWnd,"沒有找到遊戲進程","查找遊戲進程",MB_OK);
        return;
}

        if(!InjectDll(dwProcessId)){//查找到就開始注入
        MessageBox(m_hWnd,"向遊戲進程注入失敗",注入",MB_OK);
        return;
        }
}
/////////////////////////////////////////////////
DWORD FindGameProcess(LPCSTR szGameName)//負責查找遊戲進程
{
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hSnapshot==INVALID_HANDLE_VALUE)
        return 0;
PROCESSENTRY32 pe={sizeof(pe)};
DWORD dwProcessID=0;
for(BOOL fOK=Process32First(hSnapshot,&pe);fOK;fOK=Process32Next(hSnapshot,&pe)){
        if(lstrcmpi(szGameName,pe.szExeFile)==0){
          dwProcessID=pe.th32ProcessID;
          break;
        }
}
CloseHandle(hSnapshot);
return dwProcessID;
}
/////////////////////////////////////////////////
BOOL InjectDll(DWORD dwProcessId)//負責注入,參考自Jeffrey Richter《windows核心編程》
{
CString strText;
char* szLibFileRemote=NULL;

HANDLE hProcess=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,FALSE,dwProcessId);
if(hProcess==NULL){
// SetRecord("Open game process failed!");          
        return FALSE;
}
int cch=lstrlen(szDll)+1;
int cb=cch*sizeof(char);
szLibFileRemote=(char*)VirtualAllocEx(hProcess,NULL,cb,MEM_COMMIT,PAGE_READWRITE);
if(szLibFileRemote==NULL){
// SetRecord("Alloc memory to game process failed!");
        CloseHandle(hProcess);
        return FALSE;
}

if(!WriteProcessMemory(hProcess,(LPVOID)szLibFileRemote,(LPVOID)szDll,cb,NULL)){
// SetRecord("Write game process memory failed!");
        CloseHandle(hProcess);
        return FALSE;
}

PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("kernel32")),"LoadLibraryA");
if(pfnThreadRtn==NULL){
// SetRecord("Alloc memory to game process failed!");
        CloseHandle(hProcess);
        return FALSE;
}

HANDLE hThread=CreateRemoteThread(hProcess,NULL,0,pfnThreadRtn, szLibFileRemote,0,NULL);
        if(!hThread)
        {
        //         SetRecord("Create remote thread failed!");
        CloseHandle(hProcess);
        return FALSE;
        }    
        if(hThread!=NULL)
        CloseHandle(hThread);
        CloseHandle(hProcess);
        return TRUE;

}  
///////////////////////////操作遊戲內存的DLL就不貼了,大家根據不同的需要各顯神通吧///////////////////////////////////////////////////    
   
          這種方法比一個全局消息鉤子麻煩一點,但是優點是顯然易見的:可以在NP啓動前做事情,比如HOOK遊戲函數或做遊戲內存補丁。下面進入NP進程還要用到這種方法。

三、進入NP進程
        如果我們對NP有足夠的瞭解,想對它內存補丁一下,來做一些事情,哪又怎樣纔可以進入NP的進程呢?嗯,我們知道遊戲啓動流程是這樣的遊戲Main->GameGuard.des-

>GameMon.des(NP進程),其中GameGuard.des跟GameMon.des進程是遊戲Main通過調用函數CreateProcessA來創建的,上面我們說到有辦法在NP進程(GameMon.des)啓動前將我們的

DLL注入到遊戲進程裏,因此我們可以在GameMon.des啓動前掛鉤(HOOK)CreateProcessA,遊戲創建NP進程時讓NP暫停,但是遊戲本來創建NP進程時就是讓它先暫停的,這步我們

可以省了。下面是遊戲啓動NP(版本900)時傳遞的參數

          ApplicationName:C:/驚天動地Cabal Online/GameGuard/GameMon.des
          CommandLine:/x01/x58/x6d/xae/x99/x55/x57/x5d/x49/xbe/xe4/xe1/x9b/x14/xe6/x88/x57/x68/x6d/x11/xb9/x36/x73/x38/x71/x1e/x88/x46/xa9/x97/xd4/x3a/x20/x90

/x62/xae/x15/xcd/x4b/xcd/x72/x82/xbd/x75/x0a/x54/xf0/xcc/x01/xad
          CreationFlags:4
          Directory:
          其中的CommandLine好長啊,它要傳遞的參數是:一個被保護進程的pid,兩個Event的Handle,以及當前timeGetTime的毫秒數 (感謝JTR分享)。
          CreationFlags:4 查查winbase.h頭文件,發現#define CREATE_SUSPENDED 0x00000004,所以NP進程創建時就是暫停的
 
          在我們替換的CreateProcessA中,先讓遊戲創建NP進程(由於遊戲創建時NP進程本來就是暫停的,所以不用擔心NP的問題),讓遊戲進程暫停(SendMessage就可以了),然後再

向NP進程注入DLL,最後讓遊戲進程繼續。這樣我們的DLL就進入NP進程了。實現起來大概是這樣子
BOOL
WINAPI
MyCreateProcessA(//替換原來的CreateProcessA
        LPCSTR lpApplicationName,
        LPSTR lpCommandLine,
        LPSECURITY_ATTRIBUTES lpProcessAttributes,
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        BOOL bInheritHandles,
        DWORD dwCreationFlags,
        LPVOID lpEnvironment,
        LPCSTR lpCurrentDirectory,
        LPSTARTUPINFOA lpStartupInfo,
        LPPROCESS_INFORMATION lpProcessInformation
        )
{
UnhookCreateProcessA();
BOOL fRet=CreateProcessA(lpApplicationName,lpCommandLine,lpProcessAttributes,lpThreadAttributes,bInheritHandles,dwCreationFlags,
        lpEnvironment,lpCurrentDirectory,lpStartupInfo,lpProcessInformation);
RehookCreateProcessA();
          SendMessage(hwndRecv,//負責注入的窗體句柄
                  WM_HOOK_NP_CREATE,//自定義消息
                  (WPARAM)lpProcessInformation->dwProcessId,//把NP進程ID傳給負責注入的主窗體
                  NULL);
return fRet;
}

四、注意問題
        由於我們是在不破解NP的前提下對遊戲內存進行操作,所以一不小心的話,很容易就死遊戲。NP保護了遊戲進程的代碼段,所以在NP啓動後就不要再對其代碼段進行修改,要

補丁或HOOK系統函數這些都要在NP啓動前完成。當然讀寫遊戲的數據段是沒問題的,因爲遊戲本身也不斷進行這樣的操作。

由於Windows對系統底層操作採取了屏蔽的策略,因而對用戶而言,系統變得更爲安全,但這卻給衆多的硬件或者系統軟件開發人員帶來了不小的困難,因爲只要應用中涉及到底層的操作,開發人員就不得不深入到Windows的內核去編寫屬於系統級的設備驅動程序。對並行口的讀寫操作就是如此,由於Windows對系統的保護,絕對不允許任何的直接I/O動作發生,所以必須帶上*.dll、*.sys或*.vxd文件,這些文件用來讓操作系統知道有一個特定的I/O可能會被調用。系統開機後,這些文件中的內容就會加載到內存中,一旦有對應的動作發生,就會引發I/O的實際動作。

  本文只是介紹並行口作爲數字I/O口的使用,不在於介紹並行I/O口驅動的編寫。故本文中直接使用由 Yariv Kaplan 編寫的 WinIo 庫,它有如下特點:WinIo 庫通過使用內核模式下設備驅動程序和 其它一些底層編程技巧繞過 Windows 安全保護機制,允許32位 Windows 程序直接對 I/O 口進行操作。

  支持Windows 9x、Windows NT、Windows2000、WindowsXP環境;在Windows NT/2000/XP下,允許非 Administrator 用戶應用 WinIo 應用程序;不支持中斷。

  注意事項:使用這個類代碼時請確保不要與其它使用常規 Win32 調用操作並行端口的程序發生衝突。

  WinIo庫在VC應用程序中的使用

  爲了在VC中能正常使用WinIo庫,必須按以下步驟進行配置:

  (1):將WinIo.dll、WinIo.sys、WINIO.VXD三個文件放在程序可執行文件所在目錄下;

   (2):將WinIo.lib添加到工程中,WinIo.lib及winio.h文件必須放在工程目錄下;

  (3):在StdAfx.h頭文件中加入#include "winio.h"語句;

  (4):調用InitializeWinIo函數初始化WinIo驅動庫;

  (5):調用讀寫IO口的GetPortVal或SetPortVal函數;

  (6):調用ShutdownWinIo函數;

  在非管理員權限下運行,必須首先完成以下步驟:

  (1):將WinIo.dll、WinIo.sys、WINIO.VXD三個文件放在任一WinIo應用程序可執行文件所在目錄下;

  (2):以管理員或其它具有管理員權限的用戶身份登陸;

  (3):調用InstallWinIoDriver函數,第一個參數設置爲WinIo.sys文件所在目錄路徑,第二個參數設置爲false;

  (4):重新啓動系統;

  (5):以普通用戶身份登錄,現在可以調用WinIo庫函數;

  (6):當不再需要WinIo庫時,可以再次以管理員身份或其它具有管理員權限的用戶身份登陸系統,調用RemoveWinIoDriver卸載該庫;

  WinIo庫中幾個函數說明:

  (1):初始化與終止
bool _stdcall InitializeWinIo();
void _stdcall ShutdownWinIo();

  (2):安裝與卸載
bool _stdcall InstallWinIoDriver(PSTR pszWinIoDriverPath, bool IsDemandLoaded = false);
bool _stdcall RemoveWinIoDriver();

  (3):讀寫I/O口
bool _stdcall GetPortVal(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
bool _stdcall SetPortVal(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);

  GetPortVal函數從指定端口讀取一個BYTE/WORD/DWORD類型的值;
  wPortAddr是指定一個端口地址值;
  pdwPortVal爲指向一雙字節型變量的指針,該變量存儲從wPortAddr端口讀取的值;
  bSize指定讀取字節數,值可以爲1,2或4。

  SetPortVal函數向指定端口寫入一個BYTE/WORD/DWORD類型的值;
  除dwPortVal爲輸入參數,表示待寫入外,其餘個變量含義與GetPortVal相似。 

發佈了5 篇原創文章 · 獲贊 2 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章