我承認標得下得太過火。


這裡所謂的 kernel ,指的是拿掉 GUI 部份,關鍵動作之執行,筆者只試用過免費版的 GM8,所以只針對單機原理做簡易說明。至於是不是有方法可以破神盾,筆者沒研究,因摸這塊純粹是因為興趣,但筆者興趣並不包含電腦遊戲 ( 會玩電腦遊戲純粹是了解流程、開發程式而玩 )。

由於使用之 Win32 API 不少,不逐一說明,附上 MSDN 連結,有興趣請前往閱讀。

若搜尋到此篇文章,歡迎任何技術面之探討,筆者不願 blog 開始出現「索取文」之出現。

 

 

About Game Master


 

Game Master 這套軟體相信很多人都用過,在玩單機遊戲時是常拿來做數值屬性修改之軟體,本文只針對這部份做介紹。

Game Master 筆者認為最麻煩的事情是,裡面沒再弄一個簡單的 script 出來,這對需要重覆步驟之搜尋而言並非好事,同時可注意到,它最多只會搜尋到 2500 筆資料 ( 應該是定義死了 ) 。 

由於需要一款遊戲當 demo,為方便說明,這裡設計一個很簡單的遊戲,先建立 int arr1[2],再 allocate int arr2[2],當輸入 4 個數字符合 arr1[0], arr1[1], arr2[0], arr2[1] 時,則顯示猜中,否則繼續猜下去。

 

假定這執行檔就叫 game.exe,且多加了一個東西:輸出 arr1[0:1] 與 arr2[0:1] 之 address。

 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define GAME_LOOP 3

int main()
{
    int arr1[2];
    int *arr2 = (int*)malloc(2 * sizeof(int));
    int i, guess_cnt=0;
    int g1, g2, g3, g4;

    srand((unsigned)time(NULL));
    for(i=0; i<GAME_LOOP; ++i){
        arr1[0]=rand()%10, arr1[1]=rand()%10;
        arr2[0]=rand()%10, arr2[1]=rand()%10;

        printf("< ---game-%d start--->\n", i);
        printf("%p%p%p%p\n",&arr1[0], &arr1[1], &arr2[0], &arr2[1]);
        // printf("%d %d %d %d\n", arr1[0], arr1[1], arr2[0], arr2[1]);
        do{
            ++guess_cnt;
            printf("<%d> input 4 number :", guess_cnt);
            scanf("%d%d%d%d",&g1,&g2,&g3,&g4);
        }while (g1!=arr1[0] || g2!=arr1[1] ||
            g3!=arr2[0] || g4!=arr2[1]);
        printf("get wrong answer for loop %d !!\n\n", i);
    }    
    free(arr2);
    return 0;
}

 

掛住程序(Process)


 

不論是要搜尋、讀取、還是寫入,第一步驟都進行 OpenProcess,但一般較多都是從 Window name 過來,於是要先有一小段 code 將 hwnd 轉換成 hprocess 出來。

 

 const char *title = "D:\\Setting\\xp_desktop2\\mytest\\game.exe";
    HWND hwnd  = FindWindow(NULL, title);
    DWORD proc_id;
    HANDLE hProcess;
    int a, b, c, d;

    if(hwnd==NULL) {
        printf("can't find %s\n", title);
        getchar();
        return 1;
    }
    // Open Process
    GetWindowThreadProcessId(hwnd, &proc_id); 
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proc_id); 
    if(!hProcess) {
        printf("open process fail at code : %d.\n", GetLastError());
        getchar();
        return 1;
    }

 

結束時,記得要 CloseHandle(hProcess)。 

 

 

打不開?



VC 在開發時,以 IDE compile、link、execute 正常應都可執行。但一旦拿出來之後常發生 OpenProcess 失敗之情形,調用 GetLastError(),大多得到的是 5 。原因在於 OpenProcess 此函式之調用必須有一定之權限,故必須以程式碼把該權限提昇。

Win32 API 裡做這件事之函式為 OpenProcessToken,至於要查有沒有提昇成功的話,就用 LookupPrivilegeValue 去做,最後更改完權限時,還必須再用 AdjustTokenPrivileges 告知系統已提昇權限。

 

BOOL RaisePrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken))
        if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid))
            AdjustTokenPrivileges(hToken,FALSE,&tp,0,NULL,0);
    if(hToken) {// success
        CloseHandle(hToken);
        return TRUE;
    } else {
        printf("last error : %d\n", GetLastError());
        return FALSE;
    }
}

   

 

讀取

 

 

為先簡化問題,在 game.exe 裡我們已先輸出 address,假設我們已拿到這四個 address 為 0012FF5C 0012FF60 00392A10 00392A14,且我們已知道其資料型態為 int,假設 4bytes,要對其做讀取時,用到的是 ReadProcessMemory API,如下參考。

 

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

BOOL RaisePrivilege()
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken))
        if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid))
            AdjustTokenPrivileges(hToken,FALSE,&tp,0,NULL,0);
    if(hToken) {// success
        CloseHandle(hToken);
        return TRUE;
    } else {
        printf("last error : %d\n", GetLastError());
        return FALSE;
    }
}

int main()
{
    const char *title = "D:\\Setting\\xp_desktop2\\mytest\\game.exe";
    HWND hwnd  = FindWindow(NULL, title);
    DWORD proc_id;
    HANDLE hProcess;
    int a, b, c, d;

    if(hwnd==NULL) {
        printf("can't find %s\n", title);
        getchar();
        return 1;
    }
    // raise privilege
    RaisePrivilege();
    // Open Process
    GetWindowThreadProcessId(hwnd, &proc_id); 
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proc_id); 
    if(!hProcess) {
        printf("open process fail at code : %d.\n", GetLastError());
        getchar();
        return 1;
    }

    // 
    // read process memory
    //
    ReadProcessMemory(hProcess, (LPCVOID)(0x0012FF5C), (LPVOID)&a, sizeof(int), NULL );
    ReadProcessMemory(hProcess, (LPCVOID)(0x0012FF60), (LPVOID)&b, sizeof(int), NULL );
    ReadProcessMemory(hProcess, (LPCVOID)(0x00392A10), (LPVOID)&c, sizeof(int), NULL );
    ReadProcessMemory(hProcess, (LPCVOID)(0x00392A14), (LPVOID)&d, sizeof(int), NULL );
    printf("%d%d%d%d",a,b,c,d);


    //
    // close handle
    //
    CloseHandle(hProcess);
    getchar();
    return 0;
}

 

第五個參數蠻多人不知道什麼意思,它代表實際讀入之 byte 數 (若我沒意會錯 msdn 的話應是此意)。

拿出來的答案,再拿去輸入 "game.exe" 即可,發現每次都百發百中 ( 這試驗還真無聊 )。

 

 

寫入

 

 

剛剛我們的方式是從 game.exe 裡面讀 4 個值出來,再把讀出來的值輸入 game.exe 裡面去。

這次我們直接將裡面產生的 4 個數值重新改成 0 0 0 0,如此一來直接在 game.exe 裡面只要輸入4個0就過了。

這次用到的 API 也很簡單,WriteMemoryProcess ,且原始碼只改 5 行而已。

 

// 
// write process memory
// If the function succeeds, the return value is nonzero.
// If the function fails, the return value is 0 (zero). 
// To get extended error information, call GetLastError. 
// 
a=b=c=d=0;
WriteProcessMemory(hProcess, (LPVOID)(0x0012FF5C), (LPCVOID)&a, sizeof(int), NULL );
WriteProcessMemory(hProcess, (LPVOID)(0x0012FF60), (LPCVOID)&b, sizeof(int), NULL );
WriteProcessMemory(hProcess, (LPVOID)(0x00392A10), (LPCVOID)&c, sizeof(int), NULL );
WriteProcessMemory(hProcess, (LPVOID)(0x00392A14), (LPCVOID)&d, sizeof(int), NULL );

 


搜尋


 

上述之方法簡化了最難的一個步驟:搜尋。事實上要寫得完整、寫得好,勢必將 Memory Management API 與其管理機制熟讀,甚至可能要讀 Windows 系列叢書 ( 像 windows internal 、windows kernel 之類的,要寫出完整的 Game Master,需要的也不是這篇文章,而是一本又一本的好書),同時也不代表任何 process 都可以使用此法搜尋到 < 說不定 process 還有其他的保護機制,如某 page 本來是 ReadWrite,改成 NoAccess >,建議先 K 過下面幾份函式 ( 結構體 )。

 

(1) memory search api

 

SYSTEM_INFO < struct >  :  存放 processor , memory information 之簡易資訊,主要拿最小定址與最大定址做參考。 [ winbase.h -> windows.h ]

MEMORY_BASIC_INFORMATION < struct > : 取得 allocate memory 、page 之 basement, 與其保護機制 (唯讀/可讀可寫等)。[winnt.h -> windows.h ]

VirtualQueryEx  : 取得 process memory block 狀態。[windows.h / kernel32.lib]

 

另必須熟讀 MEMORY_BASIC_INFORMATION 裡 參數 DWORD AllicationProtect 之 Memory Protection Constant 代表意義。

 

 

(2) other uility api

 

StrFormatByteSize : 將一數值,依適當之 K, M, G, 轉成字串,主要拿來表示 bytes 數。這 API 似乎只有 MSVC 支援 (gcc 沒編譯過),但應不難自己寫一份。 [Shlwapi.h / Shlwapi.lib]

 

(3) memory_list

 

由於機制甚為龐大,筆者必須強調,下面只是一份 sample code,簡化了不少情況。

 

#include <windows.h>
#include <stdio.h>
#include <windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")

void ShowProtection(DWORD dwTarget)
{
    if((dwTarget & PAGE_READONLY) == PAGE_READONLY) 
        printf(", ReadOnly");
    if((dwTarget & PAGE_GUARD) == PAGE_GUARD) 
        printf(", Guard");
    if((dwTarget & PAGE_NOCACHE) == PAGE_NOCACHE) 
        printf(", NoChange");
    if((dwTarget & PAGE_READWRITE) == PAGE_READWRITE) 
        printf(", ReadWrite");
    if((dwTarget & PAGE_EXECUTE) == PAGE_EXECUTE) 
        printf(", Execute");
    if((dwTarget & PAGE_EXECUTE_READ) == PAGE_EXECUTE_READ) 
        printf(", Execute Read");
    if((dwTarget & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE) 
        printf(", Execute ReadWrite");
    if((dwTarget & PAGE_EXECUTE_WRITECOPY) == PAGE_EXECUTE_WRITECOPY) 
        printf(", Write Copy");
    if((dwTarget & PAGE_NOACCESS) == PAGE_NOACCESS) 
        printf(", No Access");
}

void memory_list(HANDLE hProcess)
{
    LPCVOID pBlock; // memory block 
    LPCVOID pEnd;
    SYSTEM_INFO si = {0}; // system information
    MEMORY_BASIC_INFORMATION mbi = {0}; // buffer of information
    char szSize[MAX_PATH];
    
    // get system information
    GetSystemInfo(&si);    

    // polling address space of hProcess
    pBlock = (LPVOID)si.lpMinimumApplicationAddress;
    while (pBlock < si.lpMaximumApplicationAddress)
    {
        // get next virtual memory block information
        if ( VirtualQueryEx(hProcess,pBlock,&mbi,sizeof(mbi))==sizeof(mbi))
        {
            pEnd = (PBYTE) pBlock + mbi.RegionSize; // calculate end and size of block
            StrFormatByteSize(mbi.RegionSize, szSize, MAX_PATH) ;

            // display address and size
            printf("[%08x,%08x] (%7s) ",pBlock, pEnd, szSize);
            // display state of block
            switch(mbi.State){
            case MEM_COMMIT : printf("Committed"); break;
            case MEM_FREE : printf("Free"); break;
            case MEM_RESERVE: printf("Reserved"); break;
            }
            // display protect status
            if(mbi.Protect==0 && mbi.State!=MEM_FREE)
                mbi.Protect = PAGE_READONLY;
            ShowProtection(mbi.Protect);

            // display type of mbi
            switch (mbi.Type)
            {
            case MEM_IMAGE:   printf(", Image"); break;
            case MEM_MAPPED:  printf(", Mapped"); break;
            case MEM_PRIVATE: printf(", Private"); break;
            }
            puts("");
            // move pointer to get next block
            pBlock = pEnd;
        }
    }
}

int main()
{
    memory_list(GetCurrentProcess());
    getchar();
    return 0;
}

 

 

(5) 其他


目前筆者所用之方式效率很差。


step 1 : 輸入 val,提昇 application 權限,找到 hProcess,OpenProcess。

step 2 : 用 VirtualQueryEx 查 process memory 狀態,只抓 MEMORY_BASIC_INFORMATION::state 為 MEM_COMMIT ,MEMORY_BASIC_INFORMATION::Protect 為 PAGE_EXECUTE_READWRITE。

step 3 : 符合 step 2 條件者,在取出來之 block,逐一 polling,查看是否與 val 相等,相等則放入一份 list 中。

step 4 : 再次輸入一個新之 val ,回到 step 2,這次判斷的部份除了符合 step 2 外,同時必須判斷是否存在 list 中 < 這裡有很多方式加速沒錯 >。


關鍵在於 step 3 速度很慢,也可能是筆者之 code 還不夠成熟,於此謹做為紀錄。

 

 

 

 

 

 

 

 

 

 

 

 

 

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()