2009-11-10

Just fight for your life ...

People say "Programming is for fun, not for money" and many people get in programming with this idea. Sometimes I have the same thought but is it really true? Programming is neither inherently fun or not fun. Career path, the lack of a programming-centric. I can't work for a little of money or work without any of the potential benefits.

Faster, better, cheaper but how... Almost programs you developed are incomplete softwares, just-runable things. Even you don't know what they are. So you develop/create what or you're just forced to write something you don't like. Really fun?

Many companies think the good way to encourage smart developers is moving them elsewhere, 'moving into management'. New opportunities, new challenges? Maybe. But I don't think experienced developers really want to move into management. Developers do that because they feel a lot of pressure while companies often don't know what to do with their developers. And the most stupid thing about this is a really good developer is worth far more than any low or middle level manager.

All fool things, I won't care. Now I'll looking for really exciting programs and enjoy lots of new thing. Try to get enough money with bored job. Professional programming is a profession and has its good points and bad points. So fight for your fun...

2009-09-19

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

// 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

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
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
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)
// -----------------------------------------------------------------------------
//  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
; 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).

// -----------------------------------------------------------------------------
//  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.
// 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ẻ

2009-09-10

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.

2009-04-26

"Từng qua tuổi 20" hay "Twentysomething: The quarter-life Crisis of Jack Lancaster"

Bắt đầu chuyển sang ngôi nhà mới.

Vậy là đã viết xong auto cho Tru Tiên online, có lẽ là OK nhất so với những auto đang có trên net, dùng được cả auto pháp bảo trong game mà chẳng có cái auto đang có nào làm được… Dù là viết để dùng personal vẫn cảm thấy vui hehe khi làm được gì đó trọn vẹn. Nhưng tự nhiên viết xong 1 thời gian rồi chẳng muốn làm gì cả. Dạo này cứ về nhà là mình lại lăn ra ngủ… chẳng hiểu. Giờ lên cty chỉ có ngáp và trông tới giờ đi về. Project thì member training chưa đủ để làm theo instructions của mình, thời gian training thì không có, mình thì lại chẳng muốn cứ lại đâm đầu vào code. Bao nhiêu năm để code những thứ củ chuối có lẽ đủ rồi, không nên cố gắng thêm nữa. Để status code for fun, không fun thì ngồi ngáp tạm vậy.

Có lẽ mình bị khủng hoảng thật rồi. Mới vừa đọc xong cuốn “Từng qua tuổi 20″ tên tiếng Anh là “Twentysomething: The quarter-life Crisis of Jack Lancaster”. Mình chẳng khùng như thằng Jack trong truyện, không béo cũng không hói đầu nhưng chắc chắn là cũng có vấn đề, năm nay 26 tuổi, sao nó trôi qua lâu quá. Ai rảnh thì đọc cuốn này thử, hơi khùng nhưng cũng có gì đó đáng đọc.

Hôm nay ngồi cài lại mấy cái máy trong nhà… mệt mỏi bây giờ đã 4h ngày Chủ nhật. Chắc mai lại nằm ngủ nữa… Mình đang chọn một cái blog mới để chuyển qua… Opera hay WordPress hay Facebook, chưa biết… Qua blog mới đã than thở rồi. Hôm nay giật mình đã gần hết tháng 4. Tự nhiên nhớ ra mình về VN đã lâu rồi mà chẳng liên lạc với ai. Còn 1 số đồ chưa gửi. Còn một gói quà sinh nhật của một cô bạn từ 1-4 năm ngoái :frown: . Còn hứa với Gam mercury đi uống cafe… từ hồi bên Nhật. Tự nhiên thấy mình chỉ lên công ty ngáp rồi về. Mình cũng chẳng đủ tiền để có thể nghỉ chừng 1/2 năm để đi lang thang. Tệ thật…

Bây giờ viết gì… Chắc sẽ bắt chước blog kỹ thuật kiểu happy programming. Cho nó đa phong cách, than thở hoài cũng chán. Lỡ đâu có thể giúp bạn bè nếu cần khỏi mất công search.

Làm cái gì? Mình bắt đầu viết auto. Một trong những ví dụ đầu tiên đụng phải là… để xem àh khi làm auto mình cần một cái progress bar có text. Search 1 vòng trên web chẳng thấy có cái nào OK cả. Hoặc có thì là progress bar viết lại từ đầu. Phát hiện thằng ProgressBar của Windows hình như không implement hàm Paint như các user control khác dù rằng nó kết thừa từ user control. Nếu override thằng OnPaint thì kết quả draw cũng như không vì sẽ bị clear kết quả draw sau khi thực hiện set giá trị. Cuối cùng mình override hàm WndProc của nó, thực hiện paint sau khi nó paint. Tức kiểm tra message gửi là paint thì thực hiện paint tiếp tại đây. Kết quả rất OK và ngắn gọn.

Hay khi bắt đầu viết auto thì phải làm gì, mình viết 1 bài về sử dụng CheatEngine, để trước chứ chưa viết hoàn chỉnh từ từ sẽ update
Sử dụng Cheat Engine để search base address dùng cho AutoPlay
Sử dụng Detours để tìm lua_State cho AutoPlay

Tự nhiên đến đây lại không biết cách post source code làm sao cho đẹp. Mình cũng không biết cách nào. Đơn giản có thể nghĩ là copy vào Word, Save as HTML rồi view source copy wa. Được không ta? Nhưng chắc cũng không giống trong Visual Studio khi dùng Resharp hay CodeSMART với Visual AssistX. Lại phải lên net search. Hic nếu 1 ngày không có google thì chắc mình không làm việc được…

Mình search ra một trang format code http://manoli.net/csharpformat/ nhưng dùng CSS không biết có paste vào blog được không. Cuối cùng phát hiện cái blog WordPress không cho dùng CSS mà phải dùng tag của nó, chỉ có điều source có comment bị format sai từa lưa http://support.wordpress.com/code/. Để sau này mình sẽ tìm cách khác. Giờ đi ngủ thôi…

2009-02-12

Convert VB6.0 sang .NET ... tương lai đen tối

Đây là lần đầu tiên mình thực hiện dự án convert VB6.0 một trong nhưng language mà mình chưa bao giờ thích. Lần đầu viết là khi thực hiện cái môn học CNPM hay QLĐAPM gì đó. Kể từ đó số project dùng VB6.0 chắc ko quá 3 ngón tay.

Vừa thoát nạn đi Nhật thì bị giao cái dự án này với nhân sự chẳng ai biết gì về khái niệm convert. Đau đầu thiệt. Một tương lai không sáng sủa. Nhưng không sao cứ gặm từ từ chắc là được. Mấy bữa này ngồi search và search lên schedule đuối ghê mà lại chết cái là tối mới cảm thấy tỉnh táo. Sáng mai lại có một cái máy chạy bằng cafe. Cố lên.

2009-01-23

日本、左様なら。。。

皆さん、

もすぐお正月ですね。
さぁ!田園へ帰ろう。皆さん、一路平安。



楽しいお正月を過ごして下さい。

2006-11-02

Cô gái ra đi từ hôm kia .......

Cô gái ra đi từ hôm kia ....... 

Hôm trước mình vô tình tìm thấy lại cuốn "Cô gái đến từ hôm qua". Phải nói là rất lâu rồi mình chẳng hề đọc một cuốn truyện nào. Ít có thời gian để đọc một cuốn truyện ... không nói chính xác là không có suy nghĩ sẽ đọc một cuốn truyện. Vậy đó không hề giống hồi xưa một chút nào. Hồi xưa mình rất hay đọc truyện và tha hồ có thời gian đọc truyện.

Và bây giờ đọc cùng một cuốn truyện như vậy, mình cũng có suy nghĩ khác hồi xưa. Hồi đó mình đọc và chỉ nghĩ rằng sao nó chuối thế, chẳng lẽ 1 chút xíu mà cũng ko đoán ra 2 con bé đó là một. Và mình đã ngồi kéo hết cuốn truyện thật nhanh để biết làm sao có thể quên một người chỉ trong vòng mấy năm...

Và bây cũng thì cũng khác, có thể hình dung làm sao thằng nhỏ đó có thể quên như vậy. Một lần đi tàu mình cũng đã từng không nhận ra mấy đứa bạn học chung lớp 9, dù ngồi rất gần :D. Mà đó là mấy năm trước rồi, chắc giờ còn quên nhiều hơn nữa. Hic đọc lại mới thấy có nhiều cái để nghĩ hơn. Lúc đọc cuốn truyện này lần đầu là thời gian suốt ngày ngồi một mình trong góc quán và chơi PS. Và lúc đó mình cũng không thể nào hiểu viết một bức thư là sao ... ặc ặc.
             
Mình cũng từng quen một người , chẳng biết có tương tự và được gọi là 'Tiểu Ly' không nữa.
Dù sao thì cái tên Tiểu Ly cũng hay, tất nhiên không phải theo cái nghĩa 'tiểu vào cái ly' ...
nếu nghĩ như vậy thì bó hand.

Tiểu Ly trong truyện là một cô bé nhỏ xíu.
Còn 'Tiểu Ly' mà mình quen là một cô bé đã vào ĐH. Trong truyện thằng nhóc thích con bé Tiểu Ly
còn mình thì không dành tình cảm cho 'Tiểu Ly' kiểu như vậy. Vậy có lẽ là không đúng rồi?? Nhưng có một chút cảm giác rất giống theo một cách khác. Mình có cảm giác bình yên, rất gần, cảm giác được đi học và dù gì có người bên cạnh trong 1 đám người xa lạ trên trường.

Rất là con nít nhưng chí ít cũng đã từng có cái gì đó rất đáng để nhớ. Và cũng giống như trong truyện cũng cảm như vừa đánh mất một điều gì đó vô cùng quý giá khi 'Tiểu Ly' ra đi và không bao giờ tìm lại được. Dẫu sao cũng tạm biệt 'Cô gái đã ra đi từ hôm kia ...', tạm biệt 'Tiểu Ly'.

Có lẽ mọi chuyện giống một câu trong bài hát 'Cô gái đến từ hôm qua'. 'Dường như là vẫn thế em không trở lại và mãi là như thế anh không trẻ lại'. Dù bài hát và cuống truyện chẳng có quan hệ gì với nhau nhưng vẫn thấy 1 chút gì liên quan. Lại là một chút thời gian để nhớ về một thời đi học, nhớ về  tình đầu haizzz không nên nhớ nhiều mới phải hic hic ...Và một điều mình nhận ra là khi nghe những bài nhạc mình cảm thấy nó hay hơn, ý nghĩa hơn so với khi nghe cách đây 5 năm trước.

Để một chiều thứ 7 không làm gì, ngồi nhà ngắm trời và nghe nhạc cho thật thỏa thích. Cảm ơn tất cả nhưng gì đã qua....


Cô gái đến từ hôm qua
Sáng tác: Trần Lê Quỳnh


Và rồi ta hứa sẽ quay trở lại,
vào một ngày mai như hai người bạn
Một ngày đã quên tất cả lại nhớ về nhau
cùng năm tháng còn ấu thơ
Và ngày hôm nay anh như đứa trẻ
của ngày hôm qua xa xôi tìm về
Lời thề tựa như ánh lửa sưởi ấm lòng anh
như chính em, cô gái đến từ hôm qua

Tình yêu đầu trôi xa dư âm để lại
và nếu thuộc về nhau em sẽ trở lại
Và anh được thấy hoa rơi như cơn
mưa tươi thắm những con đường
Dường như là vẫn thế em không trở lại
và mãi là như thế anh không trẻ lại
Dòng thời gian trôi như ánh sao băng
trong khoảng khắc qua chúng ta
Nhiều năm xa hạnh phúc anh muốn bên em
Cuộc đời này dù ngắn, nỗi nhớ quá dài
Và cũng đã đủ lớn để mong bé lại
như ngày hôm qua.