Saturday, 19 September 2009

Sử dụng Detours để tìm lua_State cho AutoPlay

Bài viết mô tả việc dùng Detours
Software package for re-routing Win32 APIs underneath applications. Under commercial release for over 10 years, Detours is licensed by over 100 ISVs and used within nearly every product team at Microsoft.
để thực hiện API hooking, cụ thể là ReAlloc nhằm lấy được base address của lua_State dùng cho AutoPlay. Lý do để thực hiện việc này là không thể dò lua_State bằng cách thông thường dùng CheatEngine (xem lại bài Sử dụng Cheat Engine để search base address dùng cho auto play).

Bài viết này không đi sâu chi tiết vào Detours, chỉ mô tả cách sử dụng Detours trong trường hợp ko dùng được CheatEngine. Cách thực hiện này có thể áp dụng trong các trường hợp có logic tương tự.

Do mấy bữa nay bận nên không thể ngồi dò base address của lua_State nên không sửa hoàn chỉnh cái auto Tru Tiên. Hôm nay cuối tuần mình sẽ ngồi viết tutorial cho phần này. Sau này nếu có ai muốn tìm giá trị của lua_State hay những địa chỉ trong trường hợp tương tự thì vẫn biết đường mà mò.

Build Lua library

Đầu tiên mình check phiên bản Lua mà game đang sử dụng (là 5.1.1), sau đó phải kiếm 1 bản lua về tại http://www.lua.org/download.html. Bản mới nhất hiện tại là 5.1.4, có thể dùng bản này hoặc 5.1.1. Sau khi download về và extract trong 1 folder Lua-5.1.1. Sau đó tạo một project static library Lua-5.1.1.vcproj, add các files trong thư mục src (nhớ bỏ 2 file lua.c và luac.c là 2 files build lua command line) vào project.

Mục đích là build static library Lua. Có thể tạo solution riêng để build lua library 1 lần thôi.















Sau khi build xong có lua-5.1.1d.lib và lua-5.1.1.lib, có thể copy lên thư mục Lib để dễ reference.

Cách thức cấp phát lua_State

Tạo một project đơn giản dạng Win32 application (dùng cái này khỏi dính dáng đến MFC) để biết lua_State được cấp phát như thế nào. Tạo Win32 application viết dạng Dialog

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Test.cpp : Defines the entry point for the application.
//

#include "stdafx.h"

// Header for Lua library
extern "C" {
#include "../Lib/Lua-5.1.1/src/lua.h"
#include "../Lib/Lua-5.1.1/src/lauxlib.h"
#include "../Lib/Lua-5.1.1/src/lualib.h"
};

//  21/Nov/2009 fixed link to Lua library
#if defined(_DEBUG)
// Debug libraries
#pragma comment(lib, "../Lib/lua-5.1.1d.lib")
#else
// Release libraries
#pragma comment(lib, "../Lib/lua-5.1.1.lib")
#endif

HINSTANCE g_hInstance = NULL;
BOOL CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPSTR lpCmdLine, int nCmdShow)
{
  // TODO: Place code here.
    g_hInstance = hInstance;
 
    HWND hDialog = 0; 
    hDialog = ::CreateDialog(hInstance, MAKEINTRESOURCE(IDD_TEST_DLG), 0, DialogProc); 
    if (!hDialog)
    {
  LPVOID lpBuffer;
  ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
   NULL, GetLastError(), 0, (LPTSTR)&lpBuffer, 0, NULL);

        ::MessageBox(NULL, (LPTSTR)lpBuffer, _T("CreateDialog"), MB_ICONEXCLAMATION | MB_OK);
  ::LocalFree(lpBuffer);

        return 1;
    } else {
  ::ShowWindow(hDialog, nCmdShow);
 }
 
    MSG  msg;
    int status;
    while ((status = ::GetMessage(&msg, 0, 0, 0)) != 0) {
        if (status == -1) {
            return -1;
  }

        if (!::IsDialogMessage(hDialog, & msg)) {
            ::TranslateMessage( & msg );
            ::DispatchMessage( & msg );
        }
    }
 
    return msg.wParam;
}

BOOL CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 WORD wId, wEvent;
 HICON hIcon = NULL;
 lua_State* state = NULL;
 TCHAR buffer[1024] = { '\0' };

    switch (message)
    {
    case WM_INITDIALOG:   
  hIcon = ::LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON));
  if(hIcon) {
   ::SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
  }
        return TRUE;

    case WM_COMMAND:
        wId    = LOWORD(wParam); 
  wEvent = HIWORD(wParam); 

  // Parse the menu selections:
  switch (wId)
  {
   case IDC_BUTTON_TEST:
    state = luaL_newstate();
    wsprintf(buffer, _T("[Process %p] "), ::GetCurrentProcessId(), state);
    ::MessageBox(NULL, buffer, _T("Test"), MB_OK);

    if (state != NULL) {
     lua_close(state);
    }
    
    break;

   case IDOK:
    ::DestroyWindow(hWnd);
    break;

   case IDCANCEL:
    ::DestroyWindow(hWnd);
    break;

   default:
    return ::DefWindowProc(hWnd, message, wParam, lParam);
  }
        return TRUE;

    case WM_DESTROY:
        ::PostQuitMessage(0);
        return TRUE;

    case WM_CLOSE:
        ::DestroyWindow(hWnd);
        return TRUE;
    }
    return FALSE;
}

Test project đơn giản chỉ là một dialog có một nút test.

// Header for Lua library
extern "C" {
#include "../Lib/Lua-5.1.1/src/lua.h"
#include "../Lib/Lua-5.1.1/src/lauxlib.h"
#include "../Lib/Lua-5.1.1/src/lualib.h"
};

//  21/Nov/2009 fixed link to Lua library
#if defined(_DEBUG)
// Debug libraries
#pragma comment(lib, "../Lib/lua-5.1.1d.lib")
#else
// Release libraries
#pragma comment(lib, "../Lib/lua-5.1.1.lib")
#endif

File lua-5.1.1d.lib là Lua đã build trong bước trước.














Khi thực hiện nhấn button Test sẽ tạo một lua_State, do đó để break point ngay đây xem có được thông tin gì không. Mình biết lua_State được tạo bởi luaL_newstate.

// Parse the menu selections:
switch (wId)
{
 case IDC_BUTTON_TEST:
  state = luaL_newstate();

Thực hiện F11 step into sẽ vào lua_newstate xem tiếp

1
2
3
4
5
LUALIB_API lua_State *luaL_newstate (void) {
  lua_State *L = lua_newstate(l_alloc, NULL);
  if (L) lua_atpanic(L, &panic);
  return L;
}

Tiếp tục step into lua_newstate với l_alloc
1
2
3
4
5
6
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  int i;
  lua_State *L;
  global_State *g;
  void *l = (*f)(ud, NULL, 0, state_size(LG));
  if (l == NULL) return NULL;

Mình để ý tiếp lua_Alloc và step into lua_Alloc f (line 5), step into lua_Alloc tại bước này
1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
  (void)ud;
  (void)osize;
  if (nsize == 0) {
    free(ptr);
    return NULL;
  }
  else
    return realloc(ptr, nsize);
}

Tới đây mình hầu như đã có thông tin mong muốn

  • Lua alloc sử dụng API realloc
  • Kích thước cấp phát là nzine

Có nghĩa là lua_State là giá trị trả về của realloc với size là 376. Con số này rất quan trọng mình sẽ ghi nhớ.










Hình: Kích thước cấp phát của lua_State

Tìm giá trị lua_State của game thông qua API hooking

Vấn đề tiếp theo là làm sao lấy được giá trị trả về của realloc trong game. Khi game bắt đầu khởi động thì lua_State đã được tạo và load các script, trước cả khi ra màn hình log in.

Khoảng thời gian này rất ngắn và không thể inject dll sau đó được. Nếu game build mode debug thì mình có thể dùng chính Visual Studio để open elementclient.exe với break point là realloc with condition (đó là cách làm lúc trước). Nhân đây mới nói chẳng hiểu Tru Tiên bản cũ sao nó dám đưa build debug làm bản thương mại ???.

Cách thực hiện theo suy nghĩ là lắng nghe tất cả các cấp phát có kích thước đúng bằng kích thước của lua_State mà bước trên mình đã có 376.

Mình sẽ dùng Detours một thư viện có sẵn để thực hiện system hook và thực hiện detour realloc. Trang chính của Detours là http://research.microsoft.com/en-us/projects/detours/ với version mới nhất là 2.1. Mình không biết nó có đổi gì hay có gì mới không nên dùng 1.5 bao gồm 2 file đơn giản detour.lib và detour.h (để trong thư mục Lib).



Libraries Lua và Detours

Bước 1: Chuẩn bị Hook và Installer

Tạo solution Detours có 2 project là Hook và Installer cùng cấp với thư mục Lib.

  1. Hook project là Win32 dll
  2. Installer project giống như test project ở trên ngoại trừ có 2 nút là Install và Uninstall.

Trong Hook project sẽ cần sử dụng detours.lib và detours.h.

Bước 2: Project Hook

Trong file Hook.h thực hiện include Detours, và tạo 2 function export install và uninstall.
Mình dùng file .def để export nên tạo file Hook.def và thêm vào project (có thể dùng __declspec(dllexport) cũng được)
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// -----------------------------------------------------------------------------
//  Document    :  Hook.h
//  Description :  Hook include file
//  Create date :  15/9/2009-13:40:07
//  Author      :  gponster (langxang)
// -----------------------------------------------------------------------------

#ifndef __HOOK_H_B10B0170_CAB8_4F19_8F66_830200F34112__
#define __HOOK_H_B10B0170_CAB8_4F19_8F66_830200F34112__

#include 
#include 
#include 

#if (_MSC_VER < 1310)
#else
#include 
#endif

#include "../Lib/detours.h"

#pragma warning(disable: 4099) 
#pragma comment(lib, "../Lib/detours.lib") 

#if defined(__cplusplus)
extern "C" {
#endif 
 void InstallHook();
 void UninstallHook();
#if defined(__cplusplus)
}
#endif

#endif // __HOOK_H_B10B0170_CAB8_4F19_8F66_830200F34112__

Nội dung Hook.def
1
2
3
4
5
6
7
8
; Hook.def : Declares the module parameters for the DLL.

LIBRARY "Hook"

EXPORTS
; Explicit exports can go here
InstallHook
UninstallHook

DLL này ko có gì đặc biệt, nếu bị lỗi SAFESEH thì tắt lý do là compiler mới bị lỗi khi link với lib build bằng compiler cũ (ở đây là Detours)
This happens when you link an .obj or .lib that contains code created by an earlier version of the compiler.
Tắt /SAFESEH:NO Project properties > Configuration Properties > Linker > Advanced










Mình sẽ thực hiện install và uninstall hook với ShellProc. Nếu bạn nào chưa hiểu rõ thì có thể tự tìm hiểu thêm (về API hooking và DLL Injection sẽ có thể ở một bài khác).

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// -----------------------------------------------------------------------------
//  Document    :  Hook.cpp
//  Description :  Hook implementation
//  Create date :  15/9/2009-13:40:07
//  Author      :  gponster (langxang)
// -----------------------------------------------------------------------------

#include "Hook.h"

HHOOK g_hHook = NULL;
HANDLE g_hInst = NULL;

DETOUR_TRAMPOLINE(int WINAPI TrampolineMessageBoxA(HWND hWnd, LPCSTR lpText, 
 LPCSTR lpCaption, UINT uType), MessageBoxA);
DETOUR_TRAMPOLINE(int WINAPI TrampolineMessageBoxW(HWND hWnd, LPCWSTR lpText, 
 LPCWSTR lpCaption, UINT uType), MessageBoxW);

typedef void* (__cdecl *ReAllocFunc)(void* memptr, size_t newsize); 
ReAllocFunc g_lpTargetReAllocMSVCRT = NULL;   // Target realloc function pointer in MSVCRT
ReAllocFunc g_lpTrampolineReAllocMSVCRT = NULL;  // Trampoline realloc function pointer in MSVCRT

ReAllocFunc g_lpTargetReAllocMSVCR90D = NULL;  // Target realloc function pointer in MSVCR90D
ReAllocFunc g_lpTrampolineReAllocMSVCR90D = NULL; // Trampoline realloc function pointer in MSVCR90D

int WINAPI PatchedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
 CHAR buffer[1024] = { 0x00 };
 ::StringCbPrintfA(buffer, sizeof(buffer), "%s - patched by Detours", lpText);

 return TrampolineMessageBoxA(hWnd, buffer, lpCaption, uType);
}

int WINAPI PatchedMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
 WCHAR buffer[1024] = { 0x00 };
 ::StringCbPrintfW(buffer, sizeof(buffer), L"%s - patched by Detours", lpText);

 return TrampolineMessageBoxW(hWnd, buffer, lpCaption, uType);
}

#define EXPECTED_SIZE (376)
void* __cdecl PatchedReAllocMSVCRT(void* memblock, size_t newsize)
{ 
 void* result = NULL;
 result = g_lpTrampolineReAllocMSVCRT(memblock, newsize);

 if (newsize == EXPECTED_SIZE)
 {
  TCHAR buffer[1024] = { 0x00 };

  ::StringCbPrintf(buffer, sizeof(buffer), 
   _T("[Process %p] MSVCRT.realloc memptr %p size %d result %p"), 
   ::GetCurrentProcessId(), memblock, newsize, result);
  ::OutputDebugString(buffer);
 }

 return result;
}

void* __cdecl PatchedReAllocMSVCR90D(void* memblock, size_t newsize)
{ 
 void* result = NULL;
 result = g_lpTrampolineReAllocMSVCR90D(memblock, newsize);
 
 if (newsize == EXPECTED_SIZE)
 {
  TCHAR buffer[1024] = { 0x00 };

  ::StringCbPrintf(buffer, sizeof(buffer), 
   _T("[Process %p] MSVCR90D.realloc memptr %p size %d result %p"), 
   ::GetCurrentProcessId(), memblock, newsize, result);
  ::OutputDebugString(buffer);
 }

 return result;
}

void Intercept()
{
 DetourFunctionWithTrampoline((PBYTE)TrampolineMessageBoxA, (PBYTE)PatchedMessageBoxA);
 DetourFunctionWithTrampoline((PBYTE)TrampolineMessageBoxW, (PBYTE)PatchedMessageBoxW);

 g_lpTargetReAllocMSVCRT = (ReAllocFunc)DetourFindFunction("MSVCRT.DLL", "realloc");
 if(g_lpTargetReAllocMSVCRT) {
  g_lpTrampolineReAllocMSVCRT = (ReAllocFunc)DetourFunction((PBYTE)g_lpTargetReAllocMSVCRT, 
   (PBYTE)PatchedReAllocMSVCRT);
 }

 g_lpTargetReAllocMSVCR90D = (ReAllocFunc)DetourFindFunction("MSVCR90D.DLL", "realloc");
 if(g_lpTargetReAllocMSVCR90D) {
  g_lpTrampolineReAllocMSVCR90D = (ReAllocFunc)DetourFunction((PBYTE)g_lpTargetReAllocMSVCR90D, 
   (PBYTE)PatchedReAllocMSVCR90D);
 }
}

void UnIntercept()
{
 DetourRemove((PBYTE)TrampolineMessageBoxA, (PBYTE)PatchedMessageBoxA);
 DetourRemove((PBYTE)TrampolineMessageBoxW, (PBYTE)PatchedMessageBoxW);

 if(g_lpTrampolineReAllocMSVCRT) {
  DetourRemove((PBYTE)g_lpTrampolineReAllocMSVCRT, (PBYTE)PatchedReAllocMSVCRT);
 }

 if (g_lpTrampolineReAllocMSVCR90D)
 {
  DetourRemove((PBYTE)g_lpTrampolineReAllocMSVCR90D, (PBYTE)PatchedReAllocMSVCR90D);
 } 
}

BOOL APIENTRY DllMain(HANDLE hInstDLL, DWORD  dwReason, LPVOID lpReserved)
{
 g_hInst = hInstDLL;
 switch (dwReason)
 {
 case DLL_PROCESS_ATTACH:
  Intercept();
  break;

 case DLL_THREAD_ATTACH:
  break;

 case DLL_THREAD_DETACH:
  break;

 case DLL_PROCESS_DETACH:
  UnIntercept();
  break;
 }
 return TRUE;
}

LRESULT CALLBACK ShellProc(int nCode, WPARAM wParam, LPARAM lParam)
{
 return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

void InstallHook()
{
 if(g_hHook == NULL) {
  g_hHook = ::SetWindowsHookEx(WH_SHELL, ShellProc, (HINSTANCE)g_hInst, 0);
 }
}

void UninstallHook()
{
 if(::UnhookWindowsHookEx(g_hHook)) {
  g_hHook = NULL;
 }
}

Để ý hàm DllMain sẽ thực hiện Intercept khi DLL_PROCESS_ATTACH và UnIntercept khi DLL_PROCESS_DETACH.

Bước 3: làm quen với Detours

Đơn giản mình sẽ demo thử với MessageBox.
Mình patch 2 version của MessageBox là MessageBoxA và MessageBoxW.

Đầu tiên khai báo với macro của Detours DETOUR_TRAMPOLINE

DETOUR_TRAMPOLINE(int WINAPI TrampolineMessageBoxA(HWND hWnd, LPCSTR lpText, 
 LPCSTR lpCaption, UINT uType), MessageBoxA);
DETOUR_TRAMPOLINE(int WINAPI TrampolineMessageBoxW(HWND hWnd, LPCWSTR lpText, 
 LPCWSTR lpCaption, UINT uType), MessageBoxW);


Hai dòng này khai báo API sẽ bị patched cũng như prototype và calling convention của các API này.

Tạo 2 function detour sao cũng được, ví dụ thêm vào thông báo 1 dòng gì đó, để buffer là 1024 chắc cũng đủ. Lưu ý calling convention của các hàm này cũng phải tương ứng với khai báo Trampoline

int WINAPI PatchedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
 CHAR buffer[1024] = { 0x00 };
 ::StringCbPrintfA(buffer, sizeof(buffer), "%s - patched by Detours", lpText);

 return TrampolineMessageBoxA(hWnd, buffer, lpCaption, uType);
}

int WINAPI PatchedMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
 WCHAR buffer[1024] = { 0x00 };
 ::StringCbPrintfW(buffer, sizeof(buffer), L"%s - patched by Detours", lpText);

 return TrampolineMessageBoxW(hWnd, buffer, lpCaption, uType);
}

Ngoài ra trong Intercept và UnIntercept sẽ thực hiện detour hay remove (Line 76, 94).

Trong source code cũng có sẵn cài đặt detour cho realloc (xem phần sau).
Hai export function là InstallHook và UninstallHook dùng ShellProc.
Sau bước này mình đã có Hook.dll với 2 export function là Install và Uninstall

Bước 4: tạo installer để cài đặt hook vào hệ thống. 

Để inject Hook.dll vào hệ thống cần tạo Installer project và dùng lại test project sửa dialog tạo 2 button install và uninstall.
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Installer.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
HINSTANCE g_hInstance = NULL;
BOOL CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

typedef void (*InstallHookFunc)();
typedef void (*UninstallHookFunc)();

InstallHookFunc g_lpInstallHook = NULL;
UninstallHookFunc g_lpUninstallHook = NULL;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPSTR lpCmdLine, int nCmdShow)
{
  // TODO: Place code here.
  ...
}

BOOL CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 WORD wId, wEvent;
 HMODULE hModule = NULL;
 HICON hIcon = NULL;

    switch (message)
    {
    case WM_INITDIALOG:  
  ::EnableWindow(::GetDlgItem(hWnd, IDC_BUTTON_UNINSTALL), FALSE);

  hModule = ::LoadLibraryA("Hook.dll");   
  if(hModule) {
   g_lpInstallHook = (InstallHookFunc)::GetProcAddress(hModule, "InstallHook");
   g_lpUninstallHook = (UninstallHookFunc)::GetProcAddress(hModule, "UninstallHook");    
  }
  
  hIcon = ::LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON));
  if(hIcon) {
   ::SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
  }

        return TRUE;

    case WM_COMMAND:
        wId    = LOWORD(wParam); 
  wEvent = HIWORD(wParam); 
  // Parse the menu selections:
  switch (wId)
  {
   case IDC_BUTTON_INSTALL:
       if(g_lpInstallHook) {
     g_lpInstallHook();
    }

    ::EnableWindow(::GetDlgItem(hWnd, IDC_BUTTON_INSTALL), FALSE);
    ::EnableWindow(::GetDlgItem(hWnd, IDC_BUTTON_UNINSTALL), TRUE);
    break;

   case IDC_BUTTON_UNINSTALL:
       if(g_lpUninstallHook) {
     g_lpUninstallHook();
     g_lpUninstallHook = NULL;
    }
    
    ::EnableWindow(::GetDlgItem(hWnd, IDC_BUTTON_INSTALL), TRUE);
    ::EnableWindow(::GetDlgItem(hWnd, IDC_BUTTON_UNINSTALL), FALSE);
    break;

   case IDOK:
    if(g_lpUninstallHook) {
     g_lpUninstallHook();
     g_lpUninstallHook = NULL;
    }
    
    ::DestroyWindow(hWnd);
    break;

   case IDCANCEL:
    if(g_lpUninstallHook) {
     g_lpUninstallHook();
     g_lpUninstallHook = NULL;
    }
    
    ::DestroyWindow(hWnd);
    break;

   default:
    return ::DefWindowProc(hWnd, message, wParam, lParam);
  }
        return TRUE;

 case WM_DESTROY:
  if (g_lpUninstallHook) {
   g_lpUninstallHook();
   g_lpUninstallHook = NULL;
  }

        ::PostQuitMessage(0);
        return TRUE;

    case WM_CLOSE:
  if(g_lpUninstallHook) {
   g_lpUninstallHook();
   g_lpUninstallHook = NULL;
  }

        ::DestroyWindow(hWnd);
        return TRUE;
    }

    return FALSE;
}

Mình khai báo prototype 2 function Install và Uninstall và 2 global function và thực hiện load từ Hook.dll, thực hiện test một cách đơn giản thôi.
typedef void (*InstallHookFunc)();
typedef void (*UninstallHookFunc)();

InstallHookFunc g_lpInstallHook = NULL;
UninstallHookFunc g_lpUninstallHook = NULL;

Thực hiện load Hook.dll ngay trong WM_INITDIALOG và lấy địa chỉ hàm luôn cho tiện. Tới đây mọi việc xong xuôi, để test mình chạy installer và install hook. 

Tới đây mình lấy cái Test.exe cũ ra xài, hoặc xài bất kỳ ứng dụng nào có MessageBox
Nếu thấy nội dung của MessageBox có thay đổi là OK.










OK vậy là đã hook API MessageBox rồi.

Bước 5: patch function realloc. realloc là function của CRT. 

CRT có version đi với OS và version của Visual Studio (để VS thực hiện debug).

API realloc trong game sẽ xài CRT ship với OS, mình có thể kiểm tra bằng cách nào cũng được, dùng Command Prompt trong Visual Studio Tools rồi xuất ra file imports.txt trong C chẳng hạn, gõ
dumpbin /imports elementclient.exe > C:\imports.txt

Trong danh sách sẽ thấy realloc trong MSVCRT.DLL.

Trong khi đó code thì mình sẽ dùng MSVCR9D.DLL của Visual Studio 2008 (bản đang dùng). Mình sẽ thực hiện patch 2 version này và test thử với Test.exe cũng tạo lua_State.

Cách làm hơi khác chút xíu, đầu tiên khai báo prototype của realloc.
typedef void* (__cdecl *ReAllocFunc)(void* memptr, size_t newsize);

Khai báo một cặp target và trampoline function
ReAllocFunc g_lpTargetReAllocMSVCRT = NULL;   // Target realloc function pointer in MSVCRT
ReAllocFunc g_lpTrampolineReAllocMSVCRT = NULL;  // Trampoline realloc function pointer in MSVCRT

ReAllocFunc g_lpTargetReAllocMSVCR90D = NULL;  // Target realloc function pointer in MSVCR90D
ReAllocFunc g_lpTrampolineReAllocMSVCR90D = NULL; // Trampoline realloc function pointer in MSVCR90D

Thực hiện tạo function detour đơn giản gọi function trampoline và log lại thông tin trả về vào debug output nếu size của realloc là 376 mà mình đã tìm ở trên.

Thêm trong Intercept và UnIntercept, mình dùng DetourFindFunction để tìm realloc, nếu tìm thấy thì thực hiện detour bằng DetourFunction
g_lpTargetReAllocMSVCRT = (ReAllocFunc)DetourFindFunction("MSVCRT.DLL", "realloc");
if(g_lpTargetReAllocMSVCRT) {
 g_lpTrampolineReAllocMSVCRT = (ReAllocFunc)DetourFunction((PBYTE)g_lpTargetReAllocMSVCRT, 
  (PBYTE)PatchedReAllocMSVCRT);
}

g_lpTargetReAllocMSVCR90D = (ReAllocFunc)DetourFindFunction("MSVCR90D.DLL", "realloc");
if(g_lpTargetReAllocMSVCR90D) {
 g_lpTrampolineReAllocMSVCR90D = (ReAllocFunc)DetourFunction((PBYTE)g_lpTargetReAllocMSVCR90D, 
  (PBYTE)PatchedReAllocMSVCR90D);
}

Trong các hàm này dùng OutputDebugString để output thông tin. Để coi được thông tin xuất bởi OutputDebugString mình dùng Dbgview có thể download tại http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx.

Đến đây chạy thử với Test, nhấn 2 lần button mình có 2 dòng debug output kết quả của patched MSVCR9D.realloc.

Tương tự vậy bật game mình cũng sẽ có được 2 kết quả tạo lua_State. Tuy nhiên không giống như test app của mình có thể nhấn nút test bất cứ lúc nào. Lua state của game chỉ thực hiện init ngay khi chạy game. Thế nên installer bắt buộc phải chạy trước tiên, thực hiện install xong xuôi rồi chúng ta mới bật game.

Chẳng hiểu sao thằng Tru Tiên này cũng debug ra đây lỗi từa lưa :D. Như vậy theo kết quả của debug viewer là mình tìm được lua_State rồi, tất nhiên đây là giá trị của lua state tại lần bật game này thôi. Nó là giá trị cấp phát động nên giá trị này chưa có ý nghĩa gì nhiều. Đến đây thì còn 1 bước nữa là tìm base address của 2 giá trị cấp phát này. Mình sẽ không tắt game client mà sẽ dùng Cheat Engine để tìm giá trị base address giống như bài trước, lưu ý giá trị hexa.


Như vậy là xong cách tìm lua_State.
Chúc mọi người cuối tuần vui vẻ

Thursday, 10 September 2009

Sử dụng Cheat Engine để search base address dùng cho auto play

Xem thêm  Sử dụng Detours để tìm lua_State cho AutoPlay

Có nhiều bạn hỏi mình cách viết auto. Source code và demo thì cũng đã có nhiều rồi. Nhưng sau cái vụ Tru Tiên đổi address trong version 24 thì nhiều bạn muốn tự mình chỉnh lại address cho nhanh chóng thì gặp khó khăn khi tìm lại các địa chỉ này dù rằng ai cũng biết sẽ cần dùng Cheat Engine.
Mình cũng chỉ giới thiệu sơ qua cách dùng Cheat Engine mà mình đã thực hiện cho cả những ai chưa quen cũng có thể thử được. Cách thực hiện cũng chỉ mấy bước và cũng không khó khăn gì.
Bước 1: Ví dụ bạn muốn search địa chỉ của HP, chúng ta đọc thông tin HP của nhân vật giá trị là 5111
Pic 1 thông tin HP 5111
Pic 1 thông tin HP 5111

Bước 2: Dùng cheat Engine chọn process và search giá trị này, là search lần đầu tiên "New Scan", giá trị là decimal ứng với HP, nên không check Hexa
Pic 2: search HP 5111
Pic 2: search HP 5111










Bước 3: Mình có rất nhiều kết quả search và thực hiện filter. Vào game tháo bớt đồ hay làm cách nào thay đổi lượng HP, ở đây mình còn 4715 máu nên bước filter mình chọn "Next Scan" với scan type là chính xác giá trị 4715 (tất nhiên có thể chọn scan type khác ví dụ bạn làm cách sao bị mất HP và chỉ cần chọn decrease tức giá trị HP giảm, nhưng nó lâu hơn)
Pic 3 kết quả search HP 5111

















Pic 4 tháo bớt đồ để thay đổi HP 4715



Pic 5 search HP mới 4715

Bước 4: Sau bước 3 mình còn 4 địa chỉ. Những địa chỉ này chỉ đúng trong game hiện tại đang chạy nên chúng ta không sử dụng được. Nhưng những địa chỉ này sẽ tham chiếu 1 địa chỉ không đổi theo 1 cách nào đó, gọi là base address cần tìm. Kinh nghiệm thì chúng ta nên để ý 2 địa chỉ gần nhau ở vùng chừng 0x09xxxxxx, vì mình biết 1 cái là HP và 1 cái là max HP cùng giá trị. Trước tiên 2 cái này vào khung kết quả bên dưới bằng cách double click hoặc select rồi nhấn button mũi tên nhỏ như trong hình. Nếu mang lại đồ thì max HP đổi còn HP thường ko đổi (trừ khi tự tăng HP).
Pic 6 sau khi filter còn 4 address ứng với 4715


Pic 7 thường HP và max HP gần nhau, base address ưu tiên địa chỉ nhỏ hơn, nhớ 2 địa chỉ này



Bước 5: Chọn một địa chỉ, giả sử mình chọn cái trên là HP (theo suy nghĩ thì cái dưới thường là max HP) rồi thực hiện "Find out what accesses this address" (những các gì truy cập tới địa chỉ này). Chỉ cần scan 1 cái thì cái kia tính theo offset, ở trên là 09xxxxBC tới 09xxxxD4.

Pic 8 thực hiện "Find out what accesses this address"



Sau khi có kết quả scan xong thì mình thực hiện chọn "More information" để có thêm thông tin. Mình không đi sâu vào chi tiết mấy cái thông tin này (nếu biết Assembly thì sẽ dễ hiểu ESI là gì). Mục đích là tìm được address thôi nên không cần tìm hiểu nhiều chỗ này. Hai thông tin cần quan tâm là cái dòng màu đỏ trong cửa sổ "More information" là offset 0x24C và giá trị địa chỉ cần tìm tiếp theo là 0x9A66F70

Pic 9 kết quả



Bước 6: Search địa chỉ kế tiếp. Mình sẽ check và hexa (đây là giá trị địa chỉ), và thực hiện "New Scan". Sau khi ra kết quả cũng có nhiều, mình sẽ lần lượt tìm kiếm vận may từ trên xuống, bắt đầu là 0x02DFFAC8. Sau khi lưu kết quả vào khung results thì mình lưu dạng pointer để có thể nhớ thêm thông tin offset 0x24C.

Pic 10 search tiếp address 0x9A66F70



Pic 11 lưu lại và chuyển thành dạng pointer






Công việc tiếp vẫn là scan "Find out what accesses this address". Nếu scan với pointer khi hiện dialog mình sẽ chọn "Find out what accesses this pointer". Tiếp tục lần theo đầu mối này

Pic 12



Bước 7: Cũng như bước 5 sang bước 6, tại đây mình sẽ có danh sách kết quả.

Pic 13


Đáng chú ý là có một dòng màu xanh lá trên cùng, điều đó cho biết là địa chỉ đó sẽ dẫn đến base address. Tất nhiên mình sẽ thực hiện search ưu tiên địa chỉ đó.

Pic 14 xuất hiện address xanh lá, ưu tiên lần theo address đó. Kết quả search address xanh lá.






Công việc vẫn là kiểm tra tiếp ưu tiên address có đánh dấu màu, đến lúc này cảm giác rất là may mắn và gần như 80% thành công rồi. Kết quả của lần scan tiếp vẫn ra 1 dòng xanh lá.

Pic 15 kết quả tiếp theo




Pic 16



Đến khi mình kiểm tra không còn tham chiếu offset nữa tức giá trị địa chỉ Cheat Engine bảo search tiếp bằng chính giá trị mình vừa scan thì nó là base address.

Pic 17 không còn offset nữa -> base address


Bước 8: Tạo một pointer lưu nguyên path của HP từ base address > 1C > 28 > 24C, max HP chúng ta có thể thấy trong lần search đầu 0x09A671D4 hơn HP là 0x09A671BC nên dễ dàng suy ra từ HP base address> 1C > 28 > 264.

Pic 18 save kết quả kèm các offset từ nãy giờ thành 1 kết quả từ base address tới HP


Pic 19 kết quả dạng pointer



Đến đây có thể kiểm tra bằng cách tắt game, rồi mở lại thử có OK không. Thêm nữa có thể thử trên 1 máy khác. Tương tự như vậy mình có thể search ra MP, max MP (thử theo cách so offset với HP trước như trong trường hợp max HP.

Pic 20



Các trường hợp đặc biệt khác


Bước 9: Tọa độ x, y, z thông thường kiểu int 4 bytes nhưng search hoài không ra nên mình thử search float và cũng ra kết quả khá đơn giản x = 0x00A45EB4 trong 1 bước.

Bước 10: Search những giá trị boolean. Trong auto play mình cần một giá trị gọi là current target, địa chỉ của monster. Mình có thể suy ra khi không có monster giá trị này có lẽ là 0, do đó không focus monster nào search giá trị 0, lần đầu search tất nhiên ra vài trăm ngàn đến vài trăm triệu kết quả.

Pic 21 search không target là 0

Pic 22 search 0

Có target phải khác 0 tức filter tăng

Pic 23 focus vào mob, filter sau khi focus monster giá trị phải tăng lên.

Pic 24 filter increase


Sau đó lại bỏ focus để filter exact 0 .... sau một vài lần thì chỉ còn lại vài trăm, sau khi loại bỏ những giá trị biến đổi liên tục chỉ còn lại một ít.

Pic 25


Pic 26 bỏ các giá trị thay đổi liên tục



Như trong hình mình sẽ chú ý nguyên 1 dãy address liên tiếp nhau, chắc dãy đó là address cần search 0x09A67F22 đến 0x09A67F27.

Pic 27 chú ý các địa chỉ liên tiếp nhau



Thực hiện scan như thao tác tìm HP ở trên ra kết quả offset 0xFB4 và address 0x09A66F70, quá quen thuộc.

Pic 28 kết quả scan

Ngoại lệ

Ngoại lệ khi thực hiện như trên mà vẫn không ra, rất là xui ??? Vậy có một cách khác hơi lâu nhưng đỡ bực bội hơi. Sau khi search lần đầu tiên và filter (bước 4 như trên), mình sẽ thực hiện "Pointer scan for this address", nên chọn default để tránh game process bị die giữa chừng.

Pic 29 thực hiện pointer scan


Pic 30


Bước tiếp là optimize lại vùng scan và tăng số thread lên cho nhanh, chừng 10 thread là được. Sau đó leo lên giường ngủ chờ đến sáng hẵng tính. Sáng dậy nếu chạy xong hoặc cảm thấy thông báo số static base khá khẩm (vài trăm ngàn) thì stop cũng được.

Pic 31


Pic 32



Bước quan trọng tiếp là save lại kết quả 1 đêm này.

Sau đó tắt game, lặp lại bước 1-4 để tìm address thể hiện HP mới, giá trị này không còn như cũ nữa. Rồi chúng ta sẽ lọc trong đám static base chọn rescan đến address mới có thì kết quả sẽ được filter lại.

Pic 33


Double click sẽ cho vào trong results, lại tắt game bật lại thì một số giá trị trong results không đúng mình sẽ bỏ đi. Cuối cùng sẽ ra base address. Dễ dàng hơn nhưng cũng không phải lúc nào cũng được. Risk cũng nhiều ví dụ máy bị sao khi mình đang ngủ. Như mình có tối con mèo làm tắt máy do để cắm điện ẩu.

Còn một phần nữa sẽ nói để hoàn thiện nhưng phải debug và cũng không dùng Cheat Engine nên sẽ nói sau (trường hợp search base address của lua state, cái này thằng nhóc em search bằng VS với mode debug nhưng giờ thì không dùng cách đó được nữa). Dù gì giờ cũng khuya rồi, chúc mọi người ngủ ngon.