如何訪問一個程式中的記憶體 (譯文二) (34千字)

看雪資料發表於2000-08-31

原標題:HOW TO ACCESS THE MEMORY OF A PROCESS
    (i.e. Game Trainer,Process Patcher etc.)
    Deep into window


    如何訪問一個程式中的記憶體(比如遊戲補丁、程式補丁等)
    深入window

作者:    NaTzGUL  a great cracker (as Quine pointed out long ago)
Email:    natzgul@hotmail.com

譯者:    DDXia[CCG]

英文聯接:http://gadget.lansg.com/fravia/natz_mp2.htm

丁丁蝦前言:
                在Crack工具八寶箱中有一種武器為記憶體動態補丁製作軟體,如果不懂它們的原理,會覺得非常的不
可思義,於是在網上無意中看到了這篇教程,揭示了記憶體動態補丁的原理,以及帶有一個簡單範例。寫的太
精彩了。希望大家看了,能有一些啟發和想法,俺丁丁蝦也知足了  :D 。
    在文章中對API的詳細解釋,因為隨便找一本中文的API大全,就可以看個明白了,何況通曉API
也是CrAcKer基本功哦!所以就沒有翻譯。
    順便說一聲,英語不是俺的母語(好象tKC也說過),也比較菜,翻譯內容大概的意思應該都對吧!
哈哈~~~~ :) 如有誤人子第的地方請指出。謝謝!


介紹:
            是的!我又勤奮了=)
            歡迎看我的新的教學篇。
            這次的熱點:如何訪問程式中的記憶體,比如寫 和讀
            其實也沒有什麼新意,但問題是在WIN95中,一個程式是不能訪問另一個程式的記憶體的。
            在這個教程中,我將演示一種方法如何繞過這個問題。
            雖然許多人都在談論這個問題,但是至今還是沒有人能解決,無論如何,都是我填補這個問題做一些貢獻
            當然也包括一些例子,就是如何使用它去破解。
           

    目標的 URL/FTP 
    目標:任何的EXE (FLAT MODEL !)


教學:
                  首先列出了我們將要用到的API (實際上這些API是用於除錯)

    KERNEL32!ReadProcessMemory
    KERNEL32!WriteProcessMemory

                    下面是 win32.hlp中的描述:
    --------------------------------------------------------------------------------
    ReadProcessMemory功能是讀取指定程式的記憶體。但是被讀的區域必須是可以訪問的,要不就操    作失敗。    

    BOOL ReadProcessMemory(

        HANDLE    hProcess,        // 被讀記憶體的程式控制程式碼
        LPCVOID lpBaseAddress,        // 開始讀的地址
        LPVOID  lpBuffer,        // 用於放資料的快取地址
    DWORD    cbRead,            //讀取的位元組數
    LPDWORD lpNumberOfBytesRead    // 從檔案中實際讀入的字元數
    );

    引數
    hProcess

    
    hProcess

    Identifies an open handle of a process whose memory is read.
    The handle must have PROCESS_VM_READ access to the process.

    lpBaseAddress

    Points to the base address in the specified process to be read.
    Before any data transfer occurs, the system verifies that all data in the
        base address and memory of the specified size is accessible for read access.
    If this is the case, the function proceeds; otherwise, the function fails.

    lpBuffer

    Points to a buffer that receives the contents from the address space of
        the specified process.

    cbRead

    Specifies the requested number of bytes to read from the specified process.

    lpNumberOfBytesRead

    Points to the actual number of bytes transferred into the specified buffer.
    If lpNumberOfBytesRead is NULL, the parameter is ignored.

    Return Value

    If the function succeeds, the return value is TRUE.
    If the function fails, the return value is FALSE.
    To get extended error information, call GetLastError.
    The function fails if the requested read operation crosses into an area of
        the process that is inaccessible.

    Remarks

    ReadProcessMemory copies the data in the specified address range from the
        address space of the specified process into the specified buffer of the
        current process.
    Any process that has a handle with PROCESS_VM_READ access can call the function.
    The process whose address space is read is typically, but not necessarily,
    being debugged.
    The entire area to be read must be accessible.
    If it is not, the function fails as noted previously.

    --------------------------------------------------------------------------------
    The WriteProcessMemory function writes memory in a specified process.
    The entire area to be written to must be accessible, or the operation fails.

BOOL WriteProcessMemory(

HANDLE    hProcess,            // handle of process whose memory is written to 
LPVOID    lpBaseAddress,        // address to start writing to
LPVOID    lpBuffer,            // address of buffer to write data to
DWORD    cbWrite,            // number of bytes to write
LPDWORD lpNumberOfBytesWritten    // actual number of bytes written
    );
    
    Parameters

    hProcess

Identifies an open handle of a process whose memory is to be written to.
The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access to the process.

    lpBaseAddress

Points to the base address in the specified process to be written to.
Before any data transfer occurs, the system verifies that all data in the base address
and memory of the specified size is accessible for write access.
If this is the case, the function proceeds; otherwise, the function fails.

    lpBuffer

Points to the buffer that supplies data to be written into the address space of the
specified process.

    cbWrite

Specifies the requested number of bytes to write into the specified process.

    lpNumberOfBytesWritten

Points to the actual number of bytes transferred into the specified process.
This parameter is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored.

    Return Value

If the function succeeds, the return value is TRUE.
If the function fails, the return value is FALSE.
To get extended error information, call GetLastError.
The function will fail if the requested write operation crosses into an area
of the process that is inaccessible.

    Remarks

WriteProcessMemory copies the data from the specified buffer in the current process
to the address range of the specified process.
Any process that has a handle with PROCESS_VM_WRITE and PROCESS_VM_OPERATION access
to the process to be written to can call the function.
The process whose address space is being written to is typically, but not necessarily,
being debugged.
The entire area to be written to must be accessible.
If it is not, the function fails as noted previously.

    --------------------------------------------------------------------------------
教程:        呼叫它用到的引數    


HANDLE    hProcess        = ?        // 程式的控制程式碼
LPVOID    lpBaseAddress,    =自己指定         // 開始讀寫的地址
LPVOID    lpBuffer,        =自己指定        //讀寫資料的緩衝地址
DWORD    cbWrite,        =自己指定        // 需要讀寫的位元組
LPDWORD lpNumberOfBytesWritten    = NULL        // 實際上讀寫的位元組

    現在看起來已經很有希望了 呵呵~~~~;)
                    關鍵是需要得到我們想訪問的程式控制程式碼。
                    一個程式可以透過呼叫"KERNEL32!GetCurrentProcess" 獲得自己的 pseudo-handle (假控制程式碼)   
                    透過這個 pseudo-handle(控制程式碼)可以獲得最大的訪問許可權。
                   
    如果我們想訪問,我們必須開啟另一個不同的程式。
    我知道有兩種方法可以獲得另一個程式的控制程式碼:
1、使用 USER32!FindWindowA,USER32!GetWindowThreadProcessId and KERNEL32!OpenProcess
2、使用KERNEL32!CreateProcess

方法一:
                Game Trainer就是透過這種方法(DDXia:可能是一個遊戲修改器)
    
                在WIN95中程式也象其他東東一樣是一個物件,這就意味著我們可以透過
    "KERNEL32!OpenProcess"得到控制程式碼,最後,不要忘記使用 "BOOL KERNEL32!CloseHandle (hProcess)" Close。否則當程式中斷時程式的映象會留在記憶體中浪費空間。
    
                  下面是 win32.hlp中的描述:
    --------------------------------------------------------------------------------

    功能:返回現有程式物件的控制程式碼

    HANDLE OpenProcess(

    DWORD    fdwAccess,    // access flag
    BOOL    fInherit,    // handle inheritance flag
    DWORD    IDProcess     // process identifier
    );

    Parameters

    fdwAccess

Specifies the access to the process object. For operating systems that support security
checking, this access is checked against any security descriptor for the target process.
Any combination of the following access flags can be specified in addition to
the STANDARD_RIGHTS_REQUIRED access flags:

Access                Description
PROCESS_ALL_ACCESS        Specifies all possible access flags for the process object.
PROCESS_CREATE_PROCESS        Used internally.
PROCESS_CREATE_THREAD        Enables using the process handle in the
                CreateRemoteThread function to create a thread in the process.
PROCESS_DUP_HANDLE        Enables using the process handle as either the source
                or target process in the DuplicateHandle function to
                duplicate a handle.
PROCESS_QUERY_INFORMATION    Enables using the process handle in the
                GetExitCodeProcess and GetPriorityClass functions
                to read information from the process object.
PROCESS_SET_INFORMATION        Enables using the process handle in the SetPriorityClass
                function to set the priority class of the process.
PROCESS_TERMINATE        Enables using the process handle in the TerminateProcess
                function to terminate the process.
PROCESS_VM_OPERATION        Enables using the process handle in the VirtualProtectEx
                and WriteProcessMemory functions to modify the virtual
                memory of the process.
PROCESS_VM_READ            Enables using the process handle in the ReadProcessMemory
                function to read from the virtual memory of the process.
PROCESS_VM_WRITE        Enables using the process handle in the
                WriteProcessMemory function to write to the virtual
                memory of the process.
SYNCHRONIZE            Windows NT: Enables using the process handle in any of
                the wait functions to wait for the process to terminate.

    fInherit

Specifies whether the returned handle can be inherited by a new process created by
the current process. If TRUE, the handle is inheritable.

    IDProcess

Specifies the process identifier of the process to open.

    Return Value

If the function succeeds, the return value is an open handle of the specified process.
If the function fails, the return value is NULL. To get extended error information,
call GetLastError.

    Remarks

The handle returned by the OpenProcess function can be used in any function
that requires a handle to a process, provided the appropriate access rights
were requested.

    --------------------------------------------------------------------------------
教程:        呼叫它用到的引數    


DWORD    fdwAccess = PROCESS_ALL_ACCESS    (Thx to Micro$oft ;) // 訪問標誌
BOOL    fInherit  = FALSE                    // 控制程式碼繼承標誌
DWORD    IDProcess = ?                        // 程式標誌



    使用 "KERNEL32!GetCurrentProcessId"得到當前程式的ID號
    為了得到另一個程式的ID號,我們必須再次執行它。
    為了找出現有的某個程式,我們得呼叫USER32!FindWindowA,它能夠透過查詢視窗的標題,
    然後返回該視窗的控制程式碼。利用這個視窗控制程式碼,我們再呼叫GetWindowThreadProcessId去獲得
                    該程式的ID。
    
    
                  下面是 win32.hlp中兩個API的描述:
    --------------------------------------------------------------------------------
The FindWindowA function retrieves the handle of the top-level window whose class name
and window name match the specified strings. This function does not search child windows.

    HWND FindWindowA(

    LPCTSTR  lpClassName,    // address of class name
    LPCTSTR  lpWindowName     // address of window name
    );    

    Parameters

    lpClassName

Points to a null-terminated string that specifies the class name or is an atom that
identifies the class-name string. If this parameter is an atom, it must be a global
atom created by a previous call to the GlobalAddAtom function.
The atom, a 16-bit value, must be placed in the low-order word of lpClassName;
the high-order word must be zero.

    lpWindowName

Points to a null-terminated string that specifies the window name (the window's title).
If this parameter is NULL, all window names match.

    Return Value

If the function succeeds, the return value is the handle of the window that has the
specified class name and window name.
If the function fails, the return value is NULL. To get extended error information,
call GetLastError.

    --------------------------------------------------------------------------------

教程:        呼叫它用到的引數    

    LPCTSTR  lpClassName    = NIL        //類名的指標
    LPCTSTR  lpWindowName    = 自己指定     // 視窗名的指標

    --------------------------------------------------------------------------------
The GetWindowThreadProcessId function retrieves the identifier of the thread that
created the specified window and, optionally, the identifier of the process that
created the window. This function supersedes the GetWindowTask function.

    DWORD GetWindowThreadProcessId(

    HWND    hWnd,        // handle of window
    LPDWORD lpdwProcessId     // address of variable for process identifier
    );    

    Parameters

    hWnd

Identifies the window.

    lpdwProcessId

Points to a 32-bit value that receives the process identifier.
If this parameter is not NULL, GetWindowThreadProcessId copies the identifier of the
process to the 32-bit value; otherwise, it does not.

    Return Value

The return value is the identifier of the thread that created the window.

    --------------------------------------------------------------------------------
教程:        呼叫它用到的引數    


HWND    hWnd        = 從 FindWindowA返回的結果    //視窗的控制程式碼
LPDWORD lpdwProcessId     = 自己指定            // 儲存程式ID變數的地址

現在我們有能力可以寫一個函式,使用它來訪問程式。這個函式我用Delphi3寫的。如果某人問我要彙編或者是
C版本的,我也會寫一個給你的,但這次我實在是太懶了;)  總之,不是很難寫的。
以下為Delphi寫的函式:


    --------------------------------------------------------------------------------    
(This function will in most cases find its use in a trainer i think) 

    type access_info = record
        error    :integer;
        hwindow    :integer;
            thread_id  :integer;
            process_id :integer;
            hprocess  :integer;
    end;

    function AccessProcess ( access_type    :integer;
                 wtitle        :string;
                 address    :integer;
                 buffer        :PByteArray;
                 b_count    :integer
                ):access_info;

    
var temp :integer;

    
begin result.error:=0;
         
  if wtitle'' then
           
    begin result.hwindow:=FindWindowA (nil,pchar(wtitle));
      if result.hwindow0 then
    begin result.thread_id:=GetWindowThreadProcessId (result.hwindow,@result.process_id);
      result.hprocess:=OpenProcess (PROCESS_ALL_ACCESS,false,result.process_id);
                            if result.hprocess0 then
                              begin temp:=0;
                                    case access_type of
0 :  ;
1 :  if not(ReadProcessMemory (result.hprocess,pointer(address),buffer,b_count,temp)) then
                                              result.error:=4;
2 :  if not(WriteProcessMemory (result.hprocess,pointer(address),buffer,b_count,temp)) then
                                              result.error:=5;
                                          else result.error:=6;
                                    end;
                                    CloseHandle (result.hprocess);
                              end
                            else result.error:=3;
                      end
                  else result.error:=2;
            end
          else result.error:=1;
    end;

    函式的訪問型別:

    0    = 僅僅是獲得資訊
    1    = 讀
    2    = 寫
    
    出錯資訊程式碼:

    0    = 正確
    1    = 視窗標題是空的。
    2    = 不能找到指定標題的視窗
    3    = 不能開啟程式
    4    = 讀錯誤
    5    = 寫錯誤
    6    = 不支援寫型別


    --------------------------------------------------------------------------------
方法2:

    在這一節中我們將去寫一個程式補丁( Process Patcher)。
                    以下是需要實行的步驟:
    - 得到命令列
    - 建立新的程式和處理命令列
                    - 等待 直到程式初始化完成
                    - 補丁程式
    - 最後終止並單獨留下新的程式
    
    程式補丁將非常有用,如果。。。。。
                   
    1、程式是被壓縮的 (如 Shrinker)
    2、程式是被加密的(如 PE-Crypt)
    3、在程式執行過程中有 CRC-Check
    4、不在乎用其他的方式修改程式
    
    使用程式補丁工具你不用關心程式本身,因為它會象這個教程中的第一種方法,進行修改程式。不
同的是我們不需要再指定視窗的標題,因為我們將透過使用KERNEL32!CreateProcess (with PROCESS_ALL_ACCESS),從被返回的結果中 獲得程式的控制程式碼。


    現在讓我們實現每一步:

    得到命令列:
    -----------------------------

    如果你已經把patcher.exe重新命名為 app.exe,也許會有些幫助。
    這種方法 app將會從補丁程式中接收到命令列,從而補丁程式對這個子程式是完全透明。

備註:    如果在exe中app執行CRC-Check,就不需要更改名字!!!!!
    否則它會在補丁程式中進行檢測,那也是符合邏輯的;)  (DDXia:也有點糊塗,於是搬出原文

相關文章