因某个顾客老是说，需要判断是否已经登陆进游戏了，所以，就有了此文。关键信息查找因为是进行状态判断，其关键字应该和“ingame”之类的相关，通过外网一番翻找，发现如下字眼“it uses flags...check ths isplayerinworld lua function.”数据定位使用工具将游戏内存转储、PE修补后，将unpack后的文件拖入ida，静候30分钟自动分析完成后，在字符串界面搜索“isplayerinworld”，定位到如下信息：.rdata:00000001426E6DD8 ; DATA XREF: .data:0000000142C3F7E0↓o .rdata:00000001426E6DEB align 10h .rdata:00000001426E6DF0 aIsplayerinworl db 'IsPlayerInWorld',0 ; DATA XREF: .data:0000000142C3F7F0↓o <-- 点击此处引用跳转 .rdata:00000001426E6E00 aGetdungeoninfo db 'GetDungeonInfo',0 ; DATA XREF: .data:0000000142C3F800↓o .rdata:00000001426E6E0F align 10h .rdata:00000001426E6E10 aReportbug db 'ReportBug',0 ; DATA XREF: .data:0000000142C3F810↓o来到如下地方：.data:0000000142C3F7E0 dq offset aIslegacydiffic ; "IsLegacyDifficulty" .data:0000000142C3F7E8 dq offset sub_1415D6010 .data:0000000142C3F7F0 dq offset aIsplayerinworl ; "IsPlayerInWorld" .data:0000000142C3F7F8 dq offset sub_1415CE330 <-----该地址即为我们需要找的对应c lua函数 .data:0000000142C3F800 dq offset aGetdungeoninfo ; "GetDungeonInfo" .data:0000000142C3F808 dq offset sub_1415D54E0 .data:0000000142C3F810 dq offset aReportbug ; "ReportBug" .data:0000000142C3F818 dq offset sub_1415D60B0 .data:0000000142C3F820 dq offset aReportsuggesti ; "ReportSuggestion" .data:0000000142C3F828 dq offset sub_1415D6100跳转到函数 1415CE330：.text:00000001415CE330 sub_1415CE330 proc near ; DATA XREF: .data:0000000142C3F7F8↓o .text:00000001415CE330 ; .pdata:000000014334A7C8↓o .text:00000001415CE330 sub rsp, 28h //内部栈空间开辟 .text:00000001415CE334 movzx edx, cs:byte_1430A3134 //获取地址1430A3134处的值，类型为byte 也就是 byte status = *(byte*)0x1430A3134 .text:00000001415CE33B shr edx, 2 //status >>2 .text:00000001415CE33E and edx, 1 //status&1 <-----此处结果就是我们需要的值 .text:00000001415CE341 call sub_1402BBAB0 //lua 压栈函数，不管 .text:00000001415CE346 mov eax, 1 .text:00000001415CE34B add rsp, 28h .text:00000001415CE34F retn .text:00000001415CE34F sub_1415CE330 endp代码实现于是，有了如下代码：/// /// 角色是否已进入游戏 /// /// 进程句柄 /// 主模块基址 /// <returns>bool</returns> bool PlayerOnLine(HANDLE handle, DWORD64 hModule) { if (handle <= 0 || hModule <= 0) return false; UINT64 ReturnLength = 0; uint8_t gameStatus = 0; ReadProcessMemory((HANDLE)handle, (PVOID64)(hModule + player_online), &gameStatus, 1, &ReturnLength); return (gameStatus >> 2) & 1; }实际测试发现，在登陆加载界面，上面代码中gameStatus第三位也是1，因此结合实际情况，将上述代码调整为：/// /// 角色是否已进入游戏 /// /// 进程句柄 /// 主模块基址 /// <returns>bool</returns> bool PlayerOnLine(HANDLE handle, DWORD64 hModule) { if (handle <= 0 || hModule <= 0) return false; UINT64 ReturnLength = 0; uint8_t gameStatus = 0; ReadProcessMemory((HANDLE)handle, (PVOID64)(hModule + player_online), &gameStatus, 1, &ReturnLength); return gameStatus == (uint8_t)0x84; }再顺便记录一下该值其他状态：EnteringGame => Status is 0x80 or 0x82 or 0x83 or 0x86; ReloadingGame => Status is 0xE4 or 0xE0 or 0xC3 or 0xC4 or 0xC6; Reloading => ReloadingGame || EnteringGame; InGame => Status == 0x84;
某游戏自动化脚本系列 （八）可能是终章了
随着某雪在应用层（R3层）增加的花指令、反调试等乱七八糟手段越来越多，该游戏数据分析工作量也越来越大，到目前我一个人利用业余时间已经完全无法搞定了。目前遇到了几个坑。一.遍地花指令比如下面代码中的 0x81、0x9A、0x55、0x78：.text:00000001408BA4E6 call loc_1408C9C8F .text:00000001408BA4EB sub ecx, 9CF78B84h .text:00000001408BA4F1 add ebp, 0FFFFFF83h .text:00000001408BA4F4 add dh, 5Bh ; '[' .text:00000001408BA4F4 ; --------------------------------------------------------------------------- .text:00000001408BA4F7 db 81h .text:00000001408BA4F8 db 9Ah .text:00000001408BA4F9 db 55h ; U .text:00000001408BA4FA db 78h ; x .text:00000001408BA4FB ; --------------------------------------------------------------------------- .text:00000001408BA4FB .text:00000001408BA4FB loc_1408BA4FB: ; CODE XREF: function_FrameScript_ExecuteBuffer+F4↑j .text:00000001408BA4FB mov eax, cs:lua_taintexpected .text:00000001408BA501 test eax, eax以及形形色色的乱序跳转、jnb+jb、jnz+jz等跳转指令与jmp的等价替换....text:00000001408B7774 nop dword ptr [rax+00h] .text:00000001408B7778 nop dword ptr [rax+rax+00000000h] .text:00000001408B7780 jnz short near ptr loc_1408B77B0+5 .text:00000001408B7782 mov al, al .text:00000001408B7784 jz short near ptr loc_1408B77B0+5 .text:00000001408B7786 neg ch .text:00000001408B7788 add edi, 92885BD0h .text:00000001408B778E pop rbx .text:00000001408B778F add dl, 0BAh .text:00000001408B7792 js near ptr loc_1408BB673+2 .text:00000001408B7798 add ebp, 9029D1EFh .text:00000001408B779E add al, 11h .text:00000001408B77A1 sub al, 76h ; 'v' .text:00000001408B77A4 add ebp, 61h ; 'a' .text:00000001408B77A7 mov edi, 801CAECh .text:00000001408B77AD mov dh, 18h .text:00000001408B77B0 .text:00000001408B77B0 loc_1408B77B0: ; CODE XREF: sub_1408B7720+60↑j .text:00000001408B77B0 ; sub_1408B7720+64↑j .text:00000001408B77B0 imul ebp, [rsi-7A4BFFEDh], -37h .text:00000001408B77B7 jz loc_1408B7DBB .text:00000001408B77BD xor r8d, r8d .text:00000001408B77C0 mov rcx, r14 .text:00000001408B77C3 lea edx, [r8-1]在这种情况下，纯手工还原的工作量太大了。二、结构体错乱UI控件父子结构关系故意紊乱化。完成lua_state的global_table逆向分析，结果发现uiobject分散分布于lua-arrayObject和node节点中，各种崩溃。附lua相关结构体定义#pragma once #include "Utils.h" #include <stdint.h> #include <string> #pragma pack(1) //结构体内存对齐方式以1字节方式对其 typedef struct LuaCommonHeader { uint64_t GCObjectPtr; unsigned long long tainted; char type; char Marked; }; union LuaValue { int Boolean; double Number; uint64_t Pointer; }; struct LuaTValue { LuaValue Value; uint32_t Type; uint32_t _unk; uint64_t tainted; }; struct LuaTKey { LuaValue Value; uint32_t Type; uint32_t _unkC; uint64_t tainted; typedef struct { LuaValue Value; uint32_t Type; uint32_t _unkC; uint64_t tainted; }tvk; uint64_t NextNodePtr; }; struct LuaNode { LuaTValue Value; LuaTKey Key; }; struct LuaTable { LuaCommonHeader Header; uint8_t Flags; /* 1<<p means tagmethod(p) is not present */ uint8_t Log2Sizenode; /* log2 of size of `node' array */ char tfill[12]; LuaTable* MetaTablePtr; LuaTValue* ArrayPtr; LuaNode* NodePtr; uint64_t lastFree; /* any free position is before this position */ uint64_t gclist = sizeof(LuaTable); uint32_t ValueCount; uint32_t NodesCount() { uint32_t tmp = Log2Sizenode; return 2*tmp; } }; //luaTString 结构体，char* 位于 &LuaTStringHeader后面 struct LuaTStringHeader { LuaCommonHeader Header; char reserved1; char reserved2; uint32_t Hash; uint64_t Length; }; #pragma pack(0) //char* hash计算 inline uint32_t getStrHash(const char* str) { auto length = (uint64_t)strlen(str); auto step = (length >> 5) + 1; for (uint64_t i = length; i >= step; i -= step) { length ^= ((length << 5) + (length >> 2)) + str[i - 1]; } return length; }; inline LuaNode getLuaNode(uint64_t nodePtr,uint32_t idx) { return utils::read<LuaNode>(nodePtr + sizeof(LuaNode) * idx); } inline LuaTValue getLuaTValue(const LuaTable& table, const char* key) { auto num = getStrHash(key); auto next = getLuaNode((uint64_t)table.NodePtr,num & (2 * table.Log2Sizenode -1)); while (next.Key.Type != 4 || memcmp(key, (const char*)(next.Key.Value.Pointer+0x20),strlen(key)) != 0) { if (next.Key.NextNodePtr == 0 || ((next.Key.NextNodePtr & 0x1) == 0x1)) break; next = utils::read<LuaNode>(next.Key.NextNodePtr); } return next.Value; }未整理的结构体定义-#define TValuefields Value value; int tt +#define TValuefields \ + lua_Value value; \ + unsigned int tt; \ + unsigned int fill; \ + unsigned long long tainted typedef struct lua_TValue { TValuefields; @@ -196,6 +205,9 @@ /* ** String headers for string table */ +/* + TString + 1c is hash?? +*/ typedef union TString { L_Umaxalign dummy; /* ensures maximum alignment for strings */ struct { @@ -288,13 +300,35 @@ ** Closures */ +/* +closure + 18h is isC (luaF_newCClosure) +closure + 28h is env (luaF_newCClosure) +closure + 30h should be BlizFunc (??) (luaF_newCClosure) +*/ + +typedef struct { + unsigned long long a; + unsigned long long b; + unsigned long long c; + unsigned long long d; +} BlizFunc; + +typedef union { + lua_CFunction cf; + BlizFunc *bf; +} BCFunction; + #define ClosureHeader \ - CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \ + CommonHeader; \ + char clhfill[6]; \ + lu_byte isC; \ + lu_byte nupvalues; \ + GCObject *gclist; \ struct Table *env typedef struct CClosure { ClosureHeader; - lua_CFunction f; + BCFunction f; TValue upvalue[1]; } CClosure; @@ -335,10 +369,18 @@ } Node; +/* + Table + 48h is sizearray + Table + 18h is flags + Table + 19h is lsizenode +*/ + typedef struct Table { CommonHeader; + char tfill1[6]; lu_byte flags; /* 1<<p means tagmethod(p) is not present */ lu_byte lsizenode; /* log2 of size of `node' array */ + char tfill2[6]; struct Table *metatable; TValue *array; /* array part */ Node *node; diff -uNr lua-5.1.4/src/lstate.h lua-5.1.4.mod/src/lstate.h --- lua-5.1.4/src/lstate.h 2008-01-03 10:20:39.000000000 -0500 +++ lua-5.1.4.mod/src/lstate.h 2021-05-21 20:17:39.547488994 -0400 @@ -65,10 +65,17 @@ /* ** `global state', shared by all threads of this state */ + +/* + global_State + 21h must be currentwhite (luaS_newlstr) + global_State + b0h must be l_registry (index2addr) +*/ + typedef struct global_State { stringtable strt; /* hash table for strings */ lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to `frealloc' */ + lu_byte isBlizFunc; lu_byte currentwhite; lu_byte gcstate; /* state of garbage collector */ int sweepstrgc; /* position of sweep in `strt' */ @@ -86,6 +93,7 @@ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC `granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ + char gsfill[16]; TValue l_registry; struct lua_State *mainthread; //0xC8 UpVal uvhead; /* head of double-linked list of all open upvalues */ @@ -93,16 +101,29 @@ TString *tmname[TM_N]; /* array with tag-method names */ } global_State; +/*0 +luastate + 38h is l_G +commonheader + 8h is tt, the type. +new strings are assigned to L->top->value.gc +luastate + 28h is L->top. +luastate + 28h is L->top->value.gc +L->top + 0h is where the new string is attached. +L->top + 8h is the TValuefields.tt +L->top + 10h is the taint. +commonheader + 11h is marked. +commonheader + 10h is tt +*/ /* ** `per thread' state */ struct lua_State { CommonHeader; + char fill0[16]; lu_byte status; StkId top; /* first free slot in the stack */ StkId base; /* base of current function */ - global_State *l_G; + global_State *l_G; /* this must be at offset 38h */ CallInfo *ci; /* call info for current function */ const Instruction *savedpc; /* `savedpc' of current function */ StkId stack_last; /* last free slot in the stack */ diff -uNr lua-5.1.4/src/ltable.c lua-5.1.4.mod/src/ltable.c --- lua-5.1.4/src/ltable.c 2007-12-28 10:32:23.000000000 -0500 +++ lua-5.1.4.mod/src/ltable.c 2021-05-21 20:17:39.583157157 -0400 @@ -74,7 +75,7 @@三、世界坐标与屏幕坐标转换具体细节可参考详细的世界坐标转屏幕坐标及正交投影矩阵的推导Vector2 camera::WorldToScreenv2(C3Vector pos) { if (camera::GCamera == nullptr) camera::Init(); CameraMgr* pCameraBase = *reinterpret_cast<CameraMgr**>(Offsets::CameraMgr); if (pCameraBase == nullptr || pCameraBase->cameraptr == nullptr) return Vector2{ 0,0 }; auto pCamera = pCameraBase->cameraptr; ImGuiIO& io = ImGui::GetIO(); RECT rc = { 0,0,io.DisplaySize.x,io.DisplaySize.y }; Vector3 difference{ pos.x - pCamera->Camera_pos.x, pos.y - pCamera->Camera_pos.y, pos.z - pCamera->Camera_pos.z }; float product = difference.x * pCamera->mat.M11 + difference.y * pCamera->mat.M12 + difference.z * pCamera->mat.M13; if (product < 0) return Vector2{ 0,0 }; Matrix3x3 inverse = pCamera->mat.Inverse(); Vector3 view{ inverse.M11 * difference.x + inverse.M21 * difference.y + inverse.M31 * difference.z , inverse.M12 * difference.x + inverse.M22 * difference.y + inverse.M32 * difference.z, inverse.M13 * difference.x + inverse.M23 * difference.y + inverse.M33 * difference.z }; Vector3 camera{ -view.y, -view.z, view.x }; Vector2 gameScreen{ (rc.right - rc.left) / 2.0f , (rc.bottom - rc.top) / 2.0f }; Vector2 aspect{ gameScreen.x / tan(((pCamera->FOV * 55.0f) / 2.0f) * M_DEG2RAD) ,gameScreen.y / tan(((pCamera->FOV * 35.0f) / 2.0f) * M_DEG2RAD) }; // 2.0f <- pCamera->FOV. Vector2 screenPos{ gameScreen.x + camera.x * aspect.x / camera.z,gameScreen.y + camera.y * aspect.y / camera.z }; if (screenPos.x < 0 || screenPos.y < 0 || screenPos.x > rc.right || screenPos.y > rc.bottom) return Vector2{ 0,0 }; return screenPos; }四、控件点击call定位（另一个朋友问我，说安全控件无法通过点击call实现鼠标左键点击，然后我就分析了一下。）通过对游戏的分析，发现游戏并不存在特殊控件，具体原因可由如下代码简单发现： RegisterUIFactoryFunction("Button", sub_1408D6F30, 0i64); RegisterUIFactoryFunction("CheckButton", sub_1408D6DA0, 0i64); RegisterUIFactoryFunction("EditBox", sub_1408D6EE0, 0i64); RegisterUIFactoryFunction("Frame", sub_1408D6E90, 0i64); RegisterUIFactoryFunction("MessageFrame", sub_1408D6F80, 0i64); RegisterUIFactoryFunction("Model", sub_1408D6D00, 0i64); RegisterUIFactoryFunction("ScrollFrame", sub_1408D6C10, 0i64); RegisterUIFactoryFunction("Slider", sub_1408D6D50, 0i64); RegisterUIFactoryFunction("SimpleHTML", sub_1408D6CB0, 0i64); RegisterUIFactoryFunction("StatusBar", sub_1408D6DF0, 0i64); RegisterUIFactoryFunction("ColorSelect", sub_1408D6E40, 0i64); RegisterUIFactoryFunction("MovieFrame", (char *)&unk_1408D6FCD + 3, 0i64); RegisterUIFactoryFunction("Browser", sub_1408D6BC0, 0i64); RegisterUIFactoryFunction("OffScreenFrame", sub_1408D6C60, 0i64);最开始，我以为是html封装的控件，但是通过控件虚函数指针获取控件类型名，发现就属于上述14种控件之一，所以我朋友无法实现左键点击功能，肯定是调用call的姿势不对。通过ida搜索字符串“click”,发现有如下内容“:Click cannot be called on Forbidden frames.”，查看其调用链，定位到如下函数：__int64 __fastcall sub_1423C9ED0(_QWORD *a1) { _BYTE *v2; // rsi __int64 result; // rax const char *v4; // rbp void (__fastcall *v5)(_BYTE *, const char *, BOOL, _QWORD); // rbx BOOL v6; // eax v2 = sub_1423CA060(a1, 0i64); if ( (v2[18] & 5) != 0 ) { luaL_error(a1, (__int64)":Click cannot be called on Forbidden frames."); result = 0i64; } else { v4 = "LeftButton"; if ( sub_14050D450((__int64)a1, 2i64) ) v4 = (const char *)sub_14050E850((__int64)a1, 2i64, 0i64); v5 = *(void (__fastcall **)(_BYTE *, const char *, BOOL, _QWORD))(*(_QWORD *)v2 + 0x220i64); v6 = sub_14050E770((__int64)a1, 3i64); v5(v2, v4, v6, 0i64); result = 0i64; } return result; }因为我们需要的是左键点击，恰好上述代码中有这么一句：v4 = "LeftButton"，因此断定该函数就是上层左键点击控件call。显而易见，v2为控件指针，因此(void (__fastcall **)(_BYTE *, const char *, BOOL, _QWORD))(*(_QWORD *)v2 + 0x220i64)为控件虚函数表第68（0x220➗8=68）个函数。通过虚函数表定位到对应函数：__int64 __fastcall sub_1423A7110(__int64 FramPtr, const char *actionName, int a3, char a4) { __int64 v4; // rbp __int64 v7; // rdi bool v8; // bl __int64 result; // rax v4 = ptr_UIFrames_Base; if ( (!a4 || !*(_BYTE *)(ptr_UIFrames_Base + 0xEBB)) //动作执行条件判断 && (*(_BYTE *)(FramPtr + 0x31C) & 1) == 0 && (*(_BYTE *)(FramPtr + 0x318) & 0xF) != 0 ) { v7 = *(_QWORD *)(ptr_UIFrames_Base + 0xE78); *(_QWORD *)(ptr_UIFrames_Base + 0xE78) = actionName; *(_BYTE *)(FramPtr + 0x31C) |= 1u; v8 = a3 != 0; sub_140848760(); //动作执行前处理 sub_1423A6330(FramPtr, 1u, actionName, v8); //第一步 sub_1423A6330(FramPtr, 0, actionName, v8); //第二步 sub_1423A6330(FramPtr, 2u, actionName, v8); //第三步 result = sub_140841540();//动作执行后处理 *(_BYTE *)(FramPtr + 0x31C) &= 0xFEu; *(_QWORD *)(v4 + 0xE78) = v7; } return result; }于是构造如下函数，注入主线程执行，测试一切正合我意！！void clickButton(uint64_t ptr) { const char tmp[] = "LeftButton"; reinterpret_cast<intptr_t(__fastcall*)(__int64 a1, const char* a2, int a3, char a4) (g_ModuleHandle + 0x23A7110) //函数指针地址 (ptr,(const char*) tmp, 0, 0); //参数 }完美解决。五、抱歉不出意外的话，本系列文章提前终止了，实在抱歉。
某游戏自动化脚本系列 （七）基础功能封装到lua--2
采用第三方库BlackBone实现主线程调用callBlackbone是windows操作系统下一款内存hack库，具体功能介绍移步其github页：https://github.com/DarthTon/Blackbone。一、BlackBone编译使用Visual Studio 2019完成编译，特别注意，C++标准需使用17版本。编译到静态库。二、使用静态库包含BlackBone.lib。在LuaFunc.cpp文件头部添加#include "..\BlackBone\BlackBone\Process\Process.h"并添加如下代码：uintptr_t g_gameBase = 0; //游戏主模块基址 blackbone::Process g_process; bool LuaFuncInit() { g_process.Attach(GetCurrentProcessId()); //g_process 初始化 g_gameBase = g_process.modules().GetMainModule().get()->baseAddress;//游戏主模块基址初始化 return true; }参考BlackBone源码中Sample项目示例：auto& remote = notepad.remote(); auto GetModuleHandleWPtr = notepad.modules().GetExport( L"kernel32.dll", "GetModuleHandleW" ); auto mainThread = notepad.threads().getMain(); //获取主线程ID //AsmFactory::GetAssembler()为AsmJit进行的封装 //AsmJit是一个完整的JIT(just In Time, 运行时刻)的针对C++语言的汇编器， //可以生成兼容x86和x64架构的原生代码，不仅支持整个x86/x64的指令集（包括传统的MMX和最新的AVX2指令集） //，而且提供了一套可以在编译时刻进行语义检查的API。 if (auto asmPtr = AsmFactory::GetAssembler(); asmPtr && mainThread && GetModuleHandleWPtr) { auto& a = *asmPtr; a.GenPrologue(); /// /// 原生函数调用 /// /// 函数指针 /// 函数参数列表 /// 函数调用约定 a.GenCall( static_cast<uintptr_t>(GetModuleHandleWPtr->procAddress), { nullptr }, cc_stdcall ); a.GenEpilogue(); uint64_t result = 0; /// /// 在任意存在的线程中执行代码 /// /// 需要执行的代码 /// 代码字节长度 /// 返回值 /// 目标线程 /// <returns>Status</returns> remote.ExecInAnyThread( a->make(), a->getCodeSize(), result, mainThread ); }根据上述示例及连接服务器函数原型，我们可以轻易写出如下代码：int ConnectToRealm(lua_State* L){ int n = lua_gettop(L); if (n < 1) return 0; unsigned int realmID = lua_tonumber(L, 1); //服务器ID if (g_gameBase + offset::LoginState_6) { if (utils::read<BYTE>(g_gameBase + offset::LoginState_6 + offset::LoginState_6_Offset)) { //根据ID获取ptr auto v2 = sub_141B6C8D0(g_gameBase + offset::LoginState_6, realmID); if (v2) { //主线程调用call //sub_141ACA530(v2 + 0x138, realmID, v2); if (auto asmPtr = blackbone::AsmFactory::GetAssembler(); asmPtr ) { auto& a = *asmPtr; a.GenPrologue(); a.GenCall(g_gameBase + offset::Login_ConnectToServer, { v2 + 0x138, realmID, v2 }, blackbone::cc_stdcall); a.GenEpilogue(); uint64_t result = 0; auto mainThread = g_process.threads().getMain(); g_process.remote().ExecInAnyThread(a->make(), a->getCodeSize(), result, mainThread); lua_pushboolean(L, 1); return 1; } } } } return 0; }三、深入聊聊扒拉了一下ExecInAnyThread的源码，如下文所示：/// /// Execute code in context of any existing thread /// /// Cde to execute /// Code size. /// Execution result /// Target thread /// <returns>Status</returns> NTSTATUS RemoteExec::ExecInAnyThread( PVOID pCode, size_t size, uint64_t& callResult, ThreadPtr& thd ) { NTSTATUS status = STATUS_SUCCESS; _CONTEXT32 ctx32 = { 0 }; _CONTEXT64 ctx64 = { 0 }; assert( _hWaitEvent != NULL ); if (_hWaitEvent == NULL) return STATUS_NOT_FOUND; // 将需要执行的pCode拷贝到目标区域 if (!NT_SUCCESS( status = CopyCode( pCode, size ) )) return status; if (_hWaitEvent) ResetEvent( _hWaitEvent ); //暂停线程 if (!thd->Suspend()) return LastNtStatus(); //call 调用外层构造 auto a = AsmFactory::GetAssembler( _process.core().isWow64() ); if (!_process.core().isWow64()) //判断目标进程指令架构 { //x64 const int count = 15; static const asmjit::GpReg regs[] = { asmjit::host::rax, asmjit::host::rbx, asmjit::host::rcx, asmjit::host::rdx, asmjit::host::rsi, asmjit::host::rdi, asmjit::host::r8, asmjit::host::r9, asmjit::host::r10, asmjit::host::r11, asmjit::host::r12, asmjit::host::r13, asmjit::host::r14, asmjit::host::r15, asmjit::host::rbp }; if (!NT_SUCCESS( status = thd->GetContext( ctx64, CONTEXT64_CONTROL, true ) )) { thd->Resume(); return status; } // // Preserve thread context // I don't care about FPU, XMM and anything else // Stack must be aligned on 16 bytes // (*a)->sub( asmjit::host::rsp, count * sizeof( uint64_t ) ); (*a)->pushf(); // Save registers for (int i = 0; i < count; i++) (*a)->mov( asmjit::Mem( asmjit::host::rsp, i * sizeof( uint64_t ) ), regs[i] ); a->GenCall( _userCode[_currentBufferIdx].ptr(), { _userData[_currentBufferIdx].ptr() } ); AddReturnWithEvent( *a, mt_mod64, rt_int32, INTRET_OFFSET ); // Restore registers for (int i = 0; i < count; i++) (*a)->mov( regs[i], asmjit::Mem( asmjit::host::rsp, i * sizeof( uint64_t ) ) ); (*a)->popf(); (*a)->add( asmjit::host::rsp, count * sizeof( uint64_t ) ); // jmp [rip] (*a)->dw( '\xFF\x25' ); (*a)->dd( 0 ); (*a)->dq( ctx64.Rip ); } else { //x86 if (!NT_SUCCESS( status = thd->GetContext( ctx32, CONTEXT_CONTROL, true ) )) { thd->Resume(); return status; } (*a)->pusha(); (*a)->pushf(); a->GenCall( _userCode[_currentBufferIdx].ptr(), { _userData[_currentBufferIdx].ptr() } ); (*a)->add( asmjit::host::esp, sizeof( uint32_t ) ); AddReturnWithEvent( *a, mt_mod32, rt_int32, INTRET_OFFSET ); //event返回 (*a)->popf(); (*a)->popa(); (*a)->push( static_cast<int>(ctx32.Eip) ); (*a)->ret(); } //设置目标线程EIP/RIP 为我们自己代码起始地址 if (NT_SUCCESS( status = _userCode[_currentBufferIdx].Write( size, (*a)->getCodeSize(), (*a)->make() ) )) { if (_process.core().isWow64()) { ctx32.Eip = static_cast<uint32_t>(_userCode[_currentBufferIdx].ptr() + size); status = thd->SetContext( ctx32, true ); } else { ctx64.Rip = _userCode[_currentBufferIdx].ptr() + size; status = thd->SetContext( ctx64, true ); } } //恢复线程运行 thd->Resume(); if (NT_SUCCESS( status )) { //等待Event事件 WaitForSingleObject( _hWaitEvent, 20 * 1000/*INFINITE*/ ); status = _userData[_currentBufferIdx].Read( INTRET_OFFSET, callResult ); } SwitchActiveBuffer(); return status; }发现，主要做了如下几件事情：暂停线程；调用call的shellcode构造及写入；保存目标线程context环境修改目标线程EIP为shellcode入口；构造的shellcode执行完成后恢复目标线程context环境；等待event返回；返回结果。鉴于此，我们可以愉快的偷偷摸摸的做很多事情啦~四、注意事项若需要调用的目标call进行了浮点运算（FPU/XMM相关寄存器的值可能发生变化），这将导致call调用前后线程context不一致，需要进行处理。
某游戏自动化脚本系列 （七）基础功能封装到lua--1
由于游戏对 frame_excuteScript以及相关内容检测太严格，因此尽量使用原生C/C++函数来实现具体功能。由于有太多功能需要主线程调用，而使用消息回调的话，太过于繁琐，因此采用第三方框架来实现主线程插入。一、整体思路将各功能封装，注册到自己lua状态机中，然后通过lua脚本来实现具体功能。二、函数申明(后续逐步补充完善)extern "C" { #include "lua/lua.h" #include "lua/lualib.h" #include "lua/lauxlib.h" } //C Function //C-Lua Function /*登录相关*/ int GetLoginState(lua_State* L); //获取登录状态 int AccountLogin(lua_State* L); //账号密码登录 int GetToken(lua_State* L); int SubmitToken(lua_State* L); //输入安全令 int ConnectToRealm(lua_State* L); // int CharSelect(lua_State* L); // int EnterWorld(lua_State* L); // int IsConnecting(lua_State* L); int IsConnectedToServer(lua_State* L); /*状态相关*/ int IsInGame(lua_State* L); int IsInDoor(lua_State* L); int IsSwimming(lua_State* L); int IsFalling(lua_State* L); int IsInBattle(lua_State* L); int IsDead(lua_State* L); int IsGhost(lua_State* L); int IsAutoCasting(lua_State* L); int IsResting(lua_State* L); int IsDriking(lua_State* L); int IsCasting(lua_State* L); int IsChannel(lua_State* L); int IsMoving(lua_State* L); int IsHorsing(lua_State* L); int IsPetExist(lua_State* L); int IsTaxing(); /*按键*/ int CPostMessage(lua_State* L); int CSendMessage(lua_State* L); /*宏*/ int MacroCreat(lua_State* L); int MacroBind2Key(lua_State* L); int MacroRunByPressKey(lua_State* L); int MacroRun(lua_State* L); /*技能*/ int SpellGetMaxMinRange(lua_State* L); int SpellGetCoolDown(lua_State* L); int SpellCast(lua_State* L); int SpellIsKnown(lua_State* L); int SpellFindSlotById(lua_State* L); int SpellCastAoe(lua_State* L); int SpellGetNameRankById(lua_State* L); int SpellLearnByNameRank(lua_State* L); int SpellLearnById(lua_State* L); /*宠物*/ int PetSummon(lua_State* L); int PetRelease(lua_State* L); int PetAttack(lua_State* L); int PetMoveTo(lua_State* L); int PetFeed(lua_State* L); /*对象管理*/ int ObjFirst(lua_State* L); int ObjNext(lua_State* L); int ObjType(lua_State* L); int ObjGuid(lua_State* L); int ObjPtr2Guid(lua_State* L); int ObjGuid2Ptr(lua_State* L); int ObjLocalPlayerPtr(lua_State* L); int ObjLocalPlayerGUID(lua_State* L); int BagCapacity(lua_State* L); int ItemInfo(lua_State* L); //Return:id,num,Durability,MaxDurability int ItemUse(lua_State* L); int ItemDestory(lua_State* L); int ItemNums(lua_State* L); int ItemSellByQuality(lua_State* L); int ItemSellByReservedId(lua_State* L); int ItemSellById(lua_State* L); int ItemBuyFromNPC(lua_State* L); int ObjLocation(lua_State* L); int ObjName(lua_State* L); int ObjTransportGuid(lua_State* L); int ObjIsBobbing(lua_State* L); int ObjGetPtrByID(lua_State* L); int UnitName(lua_State* L); int UnitHp(lua_State* L); int UnitSp(lua_State* L); int UnitLv(lua_State* L); int UnitLocation(lua_State* L); int UnitTarget(lua_State* L); int UnitIsCasting(lua_State* L); int UnitChanneledCasting(lua_State* L); int UnitCreatedByPtr(lua_State* L); int UnitPickable(lua_State* L); int UnitGatherable(lua_State* L); int UnitAttackable(lua_State* L); int UnitBuffs(lua_State* L); int UnitHpRate(lua_State* L); //自身玩家 int PlayerSelectTarget(lua_State* L); int PlayerEraseTarget(lua_State* L); int PlayerSetFace(lua_State* L); int PlayerMoveTo(lua_State* L); int PlayerGhostPostion(lua_State* L); int PlayerBuffs(lua_State* L); int PlayerTakeTaxi(lua_State* L); int PlayerHorse(lua_State* L); int PlayerMoney(lua_State* L); int PlayerGameTime(lua_State* L); //互动窗口 int InteractGossip(lua_State* L); int InteractMerchant(lua_State* L); int InteractEmail(lua_State* L); int InteractTaxi(lua_State* L); //系统消息 int GetErrMsg(lua_State* L);三、具体实现 #include "Utils.h" #include "GameDataDefine.h" #include "LuaFunc.h" uintptr_t g_gameBase = 0; int GetLoginState(lua_State* L){ int v1 = utils::read<int>(g_gameBase + offset::LoginState_1); if (utils::read<byte>(g_gameBase + offset::LoginState_2)) { v1 = 0; } lua_pushnumber(L, v1); lua_pushboolean(L, utils::read<unsigned __int8>(g_gameBase + offset::LoginState_2)); unsigned int v3 = 1; INT64 v4 = 0; if (utils::read<BYTE>(g_gameBase + offset::LoginState_3 + offset::LoginState_3_Offset) || HIBYTE(utils::read<INT32>(g_gameBase + offset::LoginState_4))) { v4 = 2; } else { v4 = 1; if (HIBYTE(utils::read<INT32>(g_gameBase + offset::LoginState_5))) v4 = 3; } lua_pushnumber(L, v4); if (!(g_gameBase + offset::LoginState_6) || !utils::read<BYTE>(g_gameBase + offset::LoginState_6+ offset::LoginState_6_Offset)) v3 = 0; lua_pushboolean(L, v3); lua_pushboolean(L, utils::read<unsigned __int8>(g_gameBase + offset::LoginState_4)); return 5; } int AccountLogin(lua_State* L){ } int GetToken(lua_State* L) { } /* 暂不明确具体功能，无其他函数调用，直接从IDA中复制出来了 */ __int64 __fastcall sub_141B6C8D0(__int64 a1, unsigned int a2) { __int64 v2; // rax __int64 v3; // r8 __int64 v4; // rcx __int64 result; // rax v2 = *(__int64*)(a1 + 0x28); v3 = a1 + 0x28; v4 = a1 + 0x28; if (!v2) goto LABEL_9; do { if (*(__int32*)(v2 + 0x20) < a2) { v2 = *(__int64*)(v2 + 0x10); } else { v4 = v2; v2 = *(__int64*)(v2 + 8); } } while (v2); if (v4 == v3 || a2 < *(__int32*)(v4 + 0x20)) LABEL_9: result = 0i64; else result = v4 + 0x28; return result; } int SubmitToken(lua_State* L){ } int ConnectToRealm(lua_State* L){ int n = lua_gettop(L); if (n < 1) return 0; unsigned int realmID = lua_tonumber(L, 1); if (g_gameBase + offset::LoginState_6) { if (utils::read<BYTE>(g_gameBase + offset::LoginState_6 + offset::LoginState_6_Offset)) { //根据ID获取ptr auto v2 = sub_141B6C8D0(g_gameBase + offset::LoginState_6, realmID); if (v2) { //主线程调用call //sub_141ACA530(v2 + 0x138, realmID, v2); } } } } int CharSelect(lua_State* L){ int n = lua_gettop(L); if (n < 1) return 0; int charIndex = lua_tonumber(L, 1) - 1; if (charIndex < 0 || charIndex >= utils::read<int>(g_gameBase + offset::MaxCharNum)) { return 0; } utils::write<int>(g_gameBase + offset::NowCharIndex, charIndex); //主线程调用 /*LOBYTE(v4) = 1; sub_14057DC00(qword_142C0F9D8, characterIndex, v4, 0, 0, 1); sub_1408864C0(0i64, 0i64, 0i64); sub_140ECA910((unsigned int)(dword_14291EDA0 + 1));*/ } int EnterWorld(lua_State* L){ //主线程调用 //if (dword_14291EDA0 >= 0 && dword_14291EDA0 < dword_142C9CB08 && qword_142C9CB10 + 0x480i64 * dword_14291EDA0) // sub_14021F4A0(); //return 0i64; } int IsConnecting(lua_State* L){ } int IsConnectedToServer(lua_State* L){ lua_pushboolean(L, utils::read<BYTE>(g_gameBase + offset::LoginState_3 + offset::LoginState_3_Offset0)); return 1; } /*状态相关*/ int IsInGame(lua_State* L){} int IsInDoor(lua_State* L){} int IsSwimming(lua_State* L){} int IsFalling(lua_State* L){} int IsInBattle(lua_State* L){} int IsDead(lua_State* L){} int IsGhost(lua_State* L){} int IsAutoCasting(lua_State* L){} int IsResting(lua_State* L){} int IsDriking(lua_State* L){} int IsCasting(lua_State* L){} int IsChannel(lua_State* L){} int IsMoving(lua_State* L){} int IsHorsing(lua_State* L){} int IsPetExist(lua_State* L){} int IsTaxing(){} /*按键*/ int CPostMessage(lua_State* L){} int CSendMessage(lua_State* L){} /*宏*/ int MacroCreat(lua_State* L){} int MacroBind2Key(lua_State* L){} int MacroRunByPressKey(lua_State* L){} int MacroRun(lua_State* L){} /*技能*/ int SpellGetMaxMinRange(lua_State* L){} int SpellGetCoolDown(lua_State* L){} int SpellCast(lua_State* L){} int SpellIsKnown(lua_State* L){} int SpellFindSlotById(lua_State* L){} int SpellCastAoe(lua_State* L){} int SpellGetNameRankById(lua_State* L){} int SpellLearnByNameRank(lua_State* L){} int SpellLearnById(lua_State* L){} /*宠物*/ int PetSummon(lua_State* L){} int PetRelease(lua_State* L){} int PetAttack(lua_State* L){} int PetMoveTo(lua_State* L){} int PetFeed(lua_State* L){} /*对象管理*/ int ObjFirst(lua_State* L){} int ObjNext(lua_State* L){} int ObjType(lua_State* L){} int ObjGuid(lua_State* L){} int ObjPtr2Guid(lua_State* L){} int ObjGuid2Ptr(lua_State* L){} int ObjLocalPlayerPtr(lua_State* L){} int ObjLocalPlayerGUID(lua_State* L){} int BagCapacity(lua_State* L){} int ItemInfo(lua_State* L){} int ItemUse(lua_State* L){} int ItemDestory(lua_State* L){} int ItemNums(lua_State* L){} int ItemSellByQuality(lua_State* L){} int ItemSellByReservedId(lua_State* L){} int ItemSellById(lua_State* L){} int ItemBuyFromNPC(lua_State* L){} int ObjLocation(lua_State* L){} int ObjName(lua_State* L){} int ObjTransportGuid(lua_State* L){} int ObjIsBobbing(lua_State* L){} int ObjGetPtrByID(lua_State* L){} int UnitName(lua_State* L){} int UnitHp(lua_State* L){} int UnitSp(lua_State* L){} int UnitLv(lua_State* L){} int UnitLocation(lua_State* L){} int UnitTarget(lua_State* L){} int UnitIsCasting(lua_State* L){} int UnitChanneledCasting(lua_State* L){} int UnitCreatedByPtr(lua_State* L){} int UnitPickable(lua_State* L){} int UnitGatherable(lua_State* L){} int UnitAttackable(lua_State* L){} int UnitBuffs(lua_State* L){} int UnitHpRate(lua_State* L){} //自身玩家 int PlayerSelectTarget(lua_State* L){} int PlayerEraseTarget(lua_State* L){} int PlayerSetFace(lua_State* L){} int PlayerMoveTo(lua_State* L){} int PlayerGhostPostion(lua_State* L){} int PlayerBuffs(lua_State* L){} int PlayerTakeTaxi(lua_State* L){} int PlayerHorse(lua_State* L){} int PlayerMoney(lua_State* L){} int PlayerGameTime(lua_State* L){} //互动窗口 int InteractGossip(lua_State* L){} int InteractMerchant(lua_State* L){} int InteractEmail(lua_State* L){} int InteractTaxi(lua_State* L){} //系统消息 int GetErrMsg(lua_State* L){}四、后续工作因需要对游戏原C/C++函数进行封装，此项工作需要大量时间和精力去完成，进度可能会慢一些。逐步完善所封装的功能函数；开展功能函数测试工作；并入第三方库实现主线程调用call(EIP插入或APC插入)。
某游戏自动化脚本系列 （六）再续--封装函数实现游戏lua_pcall调用和返回值获取
一、缘起之前文章某游戏自动化脚本系列（四）核心功能DLL实现的方法，需要在每次需要多个返回值的时候，在外层增加函数调用，但是这种方式侵入程度太高，容易被发现。二、思路通过反复研究lua 5.1.4源码，发现可以自己实现单独的api调用函数，lua源码如下：LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_checknelems(L, nargs+1); checkresults(L, nargs, nresults); if (errfunc == 0) func = 0; else { StkId o = index2adr(L, errfunc); api_checkvalidindex(L, o); func = savestack(L, o); } c.func = L->top - (nargs+1); /* function to be called */ c.nresults = nresults; status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); adjustresults(L, nresults); lua_unlock(L); return status; }errfunc可以先忽略，主要填充了 CallS结构，再调用luaD_pcall。与下面游戏lua_pcall伪代码对比。void __fastcall lua_pcall(__int64 pLua_State, int nargs, int nresults, unsigned int msgh) { __int64 v7; // r10 __int64 func; // rax void *v9; // rax __int64 v10; // r9 __int64 v11; // [rsp+30h] [rbp+0h] BYREF int v12; // [rsp+38h] [rbp+8h] if ( msgh ) { v9 = index2adr(pLua_State, msgh); v7 = *(_QWORD *)(pLua_State + 88); func = (__int64)v9 - v7; } else { v7 = *(_QWORD *)(pLua_State + 88); func = 0i64; } v10 = *(_QWORD *)(pLua_State + 40); v12 = nresults; v11 = v10 - 24i64 * (nargs + 1); luaD_pcall(pLua_State, (unsigned int)&f_call, (unsigned int)&v11, v11 - v7, func); JUMPOUT(0x140455D59i64); // 后面为混淆过的返回地址检查及其处理函数 }不难看出，我们需要做以下几件事情：所需调用的API入栈 通过lua源码#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))发现，lua_setglobal是调用的lua_setfield，而游戏中LUA_GLOBALSINDEX的值可通过index2adr来确定。参数入栈（可能有多个） 通过调用lua_push*实现。调用lua_pcall 同时通过动态修改硬编码解决返回地址检查。返回值获取 通过调用lua_to*实现。三、弊端调用的时候，需要确定具体有几个返回值，略显麻烦（别个脚本的都不需要）。
一、背包及背包物品遍历通过相关API及IDA静态分析，定位如下函数bool __fastcall sub_14171E500(__int64 a1, int a2, _QWORD *a3, int *a4, _DWORD *a5) { __int64 v8; // rdi __int64 localPlayerPtr; // rax bool v10; // cc int v12; // eax int v13; // eax int v14; // eax __int64 bagPtr; // rax unsigned __int128 bagGUID; // [rsp+20h] [rbp-18h] BYREF *a3 = 0i64; *a4 = -1; v8 = (int)a1; *a5 = 2; sub_1410E0EE0(a1); if ( !localPlayerPtr ) // localPlayerPtr 实际为sub_1410E0EE0(a1)返回值 return 0; *a4 = a2; if ( (int)v8 >= 0 ) { if ( (int)v8 >= 11 ) return *a3 && *a4 >= 0 && *a4 < *(_DWORD *)*a3; bagGUID = (unsigned int)v8 < 0xB && ((int)v8 < 4 || sub_141252550()) ? *((_OWORD *)&unk_142FA0450 + v8) : (unsigned __int128)0i64; bagPtr = ((__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD))ObjGUID2Ptr)( &bagGUID, 4i64, "D:\\BuildServer\\WoW\\1\\work\\DedicatedCheckout\\Classic\\Source\\Ui\\ContainerFrame.cpp", 614i64); if ( bagPtr ) *a3 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)bagPtr + 0x350i64))(bagPtr); if ( (int)v8 < 4 ) return *a3 && *a4 >= 0 && *a4 < *(_DWORD *)*a3; goto LABEL_23; } *a3 = localPlayerPtr + 0x28640; // item-guid数组指针 switch ( (_DWORD)v8 ) { case 0xFFFFFFFB: v14 = *a4 + 0x4B; *a4 = v14; v10 = v14 <= 81; break; case 0xFFFFFFFD: v13 = *a4 + 0x5E; *a4 = v13; v10 = v13 <= 125; break; case 0xFFFFFFFE: v12 = *a4 + 0x2F; *a4 = v12; if ( v12 > 74 ) return 0; LABEL_23: *a5 = 3; return *a3 && *a4 >= 0 && *a4 < *(_DWORD *)*a3; case 0xFFFFFFFF: *a4 += 0x17; v10 = *a4 <= (int)(sub_1412544D0(*a3) + 23); break; default: return *a3 && *a4 >= 0 && *a4 < *(_DWORD *)*a3; } if ( !v10 ) return 0; return *a3 && *a4 >= 0 && *a4 < *(_DWORD *)*a3; }根据上述伪代码，大胆猜测：&unk_142FA0450 + v8为背包指针或者GUID；*a3 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)bagPtr + 0x350i64))(bagPtr)与背包中物品有关系。通过Cheat Engine验证，发现：&unk_142FA0450 + bagIndex为背包GUID。虚函数：bagPtr + 0x350为lea rax, qword ptr ds:[rcx+0x00001730]，观察 bagPtr+0x1730处值，发现*(bagPtr+0x1730+8)为背包物品GUID数组。考虑到游戏存在系统自带背包，在上述伪代码中，背包物品指针为localPlayerPtr + 0x28640因此，有了如下遍历函数：void DbgWin::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 std::string str = ""; for (auto i = -1; i < 5; i++) { if (utils::read<DWORD64>(g_ModuleHandle + offset::BagGUID + i * 16) == 0) //背包为空 { if (i == -1) //系统自带背包 { DWORD64 playerAddr = getLocalPlayerPtr(); auto ptr = utils::read<DWORD64>(playerAddr + 0x28648); str += "ptr:" + utils::ptr2str(ptr)+"\tCapacity:20 ---->默认背包\r\n"; for (int j = 0; j < 20; j++) { if (utils::read<DWORD64>(ptr + (j + 23) * 16) == 0) //物品为空 continue; auto ItemPtr = guid2ptr((char*)(ptr + (j + 23) * 16)); //背包ptr auto pDescript = utils::read<DWORD64>(ItemPtr + offset::StorageField); auto id = utils::read<DWORD64>(pDescript + offset::ItemID); auto count = utils::read<DWORD64>(pDescript + offset::ItemStackCount); str += " (" + std::to_string(i + 1) + "," + std::to_string(j + 1) + ")\tid:" + std::to_string(id) + "\tcount:" + std::to_string(count) + "\r\n"; } } continue; } auto ptr = guid2ptr((char*)(g_ModuleHandle + offset::BagGUID + i * 16)); //背包ptr str+="ptr:"+ utils::ptr2str(ptr) ; auto size = utils::read<uint32_t>(ptr + offset::BagItemOffset + offset::BagItemsCapacity); auto itemguidptr = utils::read<DWORD64>(ptr + offset::BagItemOffset + offset::BagItemGUIDOffset); str += "\tCapacity:" + std::to_string(size) +"\tItemGuidPtrs:"+ utils::ptr2str(itemguidptr)+ "\r\n"; for (auto j = 0; j < size; j++) { if (utils::read<DWORD64>(itemguidptr + j * 16) == 0) //物品为空 continue; auto ItemPtr = guid2ptr((char*)(itemguidptr + j * 16)); //背包ptr auto pDescript = utils::read<DWORD64>(ItemPtr + offset::StorageField); auto id = utils::read<DWORD64>(pDescript + offset::ItemID); auto count = utils::read<DWORD64>(pDescript + offset::ItemStackCount); str += " (" + std::to_string(i+1)+"," + std::to_string(j + 1)+")\tid:" + std::to_string(id) +"\tcount:" + std::to_string(count) + "\r\n"; } } SetDlgItemText(IDC_EDIT1, str.c_str()); }运行效果如下图所示：二、装备物品遍历由于人物只能佩戴19件装备，有了如下遍历。void DbgWin::OnBnClickedButton8() { // TODO: 在此添加控件通知处理程序代码 DWORD64 playerAddr = getLocalPlayerPtr(); auto ptr = utils::read<DWORD64>(playerAddr + 0x28648); std::string str = "ptr:" + utils::ptr2str(ptr) + "\r\n"; for (int i = 0; i < 19; i++) { char guid[128] = "\0"; g_Func_guid2Str((unsigned char*)(ptr + i * 16), guid, 128);//游戏guid转str函数 str += "ptr:" + utils::ptr2str(ptr + i * 16)+"\tindex:" + std::to_string(i+1) + "\tguid:" + std::string(guid) + "\r\n"; } SetDlgItemText(IDC_EDIT1, str.c_str()); }其效果如下图所示：三、buff、debuf遍历void DbgWin::OnBnClickedButton15() { // TODO: 在此添加控件通知处理程序代码 DWORD64 playerAddr = getLocalPlayerPtr(); auto count = utils::read<DWORD>(playerAddr + offset::AuraCount1); std::string str = ""; for (int i = 0; i < count; i++) { auto aruaID = utils::read<DWORD>(playerAddr + i * offset::AuraSize + offset::AuraTable1 + offset::AuraSpellId); auto timeLeft = utils::read<DWORD>(playerAddr + i * offset::AuraSize + offset::AuraTable1 + offset::AuraTimeLeft); auto flags = utils::read<UINT16>(playerAddr + i * offset::AuraSize + offset::AuraTable1 + offset::AuraFlags); auto lv = utils::read<UINT16>(playerAddr + i * offset::AuraSize + offset::AuraTable1 + offset::AuraLevel); char guid[128] = "\0"; g_Func_guid2Str((unsigned char*)(playerAddr + i * offset::AuraSize + offset::AuraTable1 + offset::AuraOwnerGUID), guid, 128); if(aruaID!=0) str += "ID:" + std::to_string(aruaID) + "\ttimeLeft:" + std::to_string(timeLeft) + "\tflags:" + std::to_string(flags) + "\tlv:" + std::to_string(lv) + "\towner:" + std::string(guid)+"\r\n"; } SetDlgItemText(IDC_EDIT1, str.c_str()); }游戏中unit对象（type为3）buff遍历方式同上。
某游戏自动化脚本系列 （番外篇）聊聊数据分析
一、可用资源GitHubWoWWiki | FandomownedcoreWoW.tools | ExportTrinityCorerecastnavigationarticls show how to make bot二、工具dumpwowCheatEngineX64DbgIDA/ghidra...三、分析过程通过相关网站获取信息。使用dump工具修复并dump可执行文件。使用IDA静态分析可执行文件，使用CheatEngine进行动态调试。使用VS2019 编写C++（c#）程序，验证数据准确性。四、IDA分析示例情景：需要分析游戏中背包物品及其信息存储方式。通过查询API，发现GetContainerItemInfo返回了足够的内容：纹理、数量、ID等等。于是祭出IDA，导入可执行文件，通过字符串“GetContainerItemInfo”定位其对应的lua c函数。如下图所示：其中，sub_1417158F0就是接下来需要分析的函数。lua函数参数的传递，是使用其lua堆栈（lua_state指针及序号）来进行的。考虑到API GetContainerItemInfo的参数和返回值：ParametersbagID：Number - number of the bag the item is in, e.g. 0 for your backpackslot：Number - slot number of the bag item you want the info for.Returnstexture：String - the texture for the item in the specified bag slotitemCount：Number - the number of items in the specified bag slotlocked：Boolean - 1 if the item is locked by the server, nil otherwise.quality：Number - the numeric quality of the itemreadable：Boolean - 1 if the item can be "read" (as in a book)lootable：Boolean - 1 if the item is a temporary container containing items that can be looted; otherwise nil.itemLink：a hyperlink for the item.因此，对于其F5分析后的伪代码，有了如下结果：__int64 __fastcall sub_1417158F0(__int64 a1) { int Itemquality; // edi int slotIndex; // ebx int bagIndex; // eax __int64 v5; // rax __int64 itemPtr; // rbx unsigned int Itemtexture; // eax unsigned int v8; // ebp __int64 itemlocked; // rdx __int128 v10; // xmm1 __int128 v11; // xmm0 __int128 v12; // xmm1 __int64 v13; // r14 unsigned int v14; // eax unsigned int v15; // eax int v16; // edx int v17; // er8 int v18; // er9 __int64 v19; // rax unsigned __int8 Itemlootable; // al unsigned __int8 itemLink; // al __int64 result; // rax char v23; // [rsp+20h] [rbp-B8h] __int64 v24[6]; // [rsp+30h] [rbp-A8h] BYREF __int128 v25[4]; // [rsp+60h] [rbp-78h] BYREF __int64 v26; // [rsp+A0h] [rbp-38h] unsigned int v27; // [rsp+E8h] [rbp+10h] BYREF int v28; // [rsp+F0h] [rbp+18h] BYREF __int64 v29; // [rsp+F8h] [rbp+20h] BYREF if ( (unsigned int)sub_14045E150() && (Itemquality = 2, (unsigned int)sub_14045E150()) ) { //确定下面两行代码是获取 参数以后，同时也能明晰：sub_14045F500 为 lua_tonumber(),而且 a1 为lua_state指针。 slotIndex = sub_14045F500(a1, 2i64); //此处获取第二个参数，肯定是slot bagIndex = sub_14045F500(a1, 1i64); //此处获取第一个参数，肯定是bagID if ( sub_14171E500((unsigned int)(bagIndex - 1), slotIndex - 1, &v29, (int *)&v27, &v28) && (v5 = ItemLoc2Ptr(v29, v27), (itemPtr = v5) != 0) )//此处if判断语句，需要结合深层调用分析，发现v5为对应物品指针（objectmanager链表中obj指针），同时分析此处函数调用，又能获取物品在objectmanager链表中所属关系的表述：物品（guid）存放在背包(guid)中，背包(guid)属于玩家(guid)。 { //考虑到a1为lua_state指针，以及该函数正常需要返回8个参数，可知sub_14080ED20(a1, Itemtexture)开始将结果push到lua堆栈中。再根据返回值顺序，确定各值意义 Itemtexture = sub_1411A4200(v5, 0i64); //此处为获取 texture sub_14080ED20(a1, Itemtexture); lua_pushnumber(a1, (double)*(int *)(*(_QWORD *)(itemPtr + 0x188) + 0x40i64));//此处为itemNum //从而，*(int *)(*(_QWORD *)(itemPtr + 0x188) + 0x40i64)=>物品数量 v8 = 0; if ( (*(_DWORD *)(*(_QWORD *)(itemPtr + 0x188) + 0x5Ci64) & 0x800) != 0 || (itemlocked = 0i64, *(_WORD *)(itemPtr + 0x8D2)) ) // =>物品是否被服务器锁定 { itemlocked = 1i64; } sub_14045E590(a1, itemlocked); v10 = *(_OWORD *)(itemPtr + 0x950); v25[0] = *(_OWORD *)(itemPtr + 0x940); v11 = *(_OWORD *)(itemPtr + 0x960); v25[1] = v10; v12 = *(_OWORD *)(itemPtr + 0x970); v25[2] = v11; v26 = *(_QWORD *)(itemPtr + 0x980); v25[3] = v12; v13 = sub_140853290(v25); v14 = sub_14171E7C0(itemPtr, 4i64); //=》此处获取物品品质，同时根据下面switch语句，可知物品品质分为0->4共5个等级。 if ( v14 ) { switch ( HIBYTE(v14) ) { case 0u: Itemquality = 0; break; case 1u: Itemquality = 1; break; case 2u: break; case 3u: Itemquality = 3; break; case 4u: Itemquality = 4; break; case 5u: Itemquality = 5; break; default: goto LABEL_17; } } else if ( v13 ) { Itemquality = sub_1405B2130(v13, *(unsigned int *)(*(_QWORD *)(itemPtr + 16) + 16i64)); //=》此处获取物品品质 } else { LABEL_17: Itemquality = -1; } lua_pushnumber(a1, (double)Itemquality); v24[0] = 1i64; if ( (unsigned int)sub_1411A5660(itemPtr, v24) || (*(_DWORD *)(*(_QWORD *)(itemPtr + 392) + 92i64) & 0x200) != 0 ) v8 = 1; sub_14045E590(a1, v8); v15 = (**(__int64 (__fastcall ***)(__int64, _QWORD))(itemPtr + 384))(itemPtr + 384, 0i64); sub_14045E590(a1, (v15 >> 2) & 1); v19 = sub_141961CE0(itemPtr, v16, v17, v18, v23); sub_14045E860(a1, v19); Itemlootable = sub_14171F030(itemPtr); sub_14045E590(a1, Itemlootable); itemLink = sub_1411A7C90(itemPtr); sub_14045E590(a1, itemLink); if ( v13 ) sub_14045E750(a1, LODWORD(v25[0])); else sub_14045E820(a1); result = 10i64; } else { result = 0i64; } } else { sub_140460180(a1, "Usage: GetContainerItemInfo(index, slot)"); result = 0i64; } return result; }因此，在不进行动态分析的情况下，就完成了所需数据的获取。
某游戏自动化脚本系列 （六）基本功能测试
一、模块隐藏class XHideDll { public: XHideDll(); ~XHideDll(); //static PVOID GetProcessPEB(); static void PrintModuleList(); static bool HideInLoadOrderLinks(unsigned long long dllBase); static bool HideInMemoryOrderLinks(unsigned long long dllBase); static bool HideInInitializationOrderLinks(unsigned long long dllBase); static void HideModule(unsigned long long hModule, bool DeleteAfter); static void LM_CreateFakeThread(unsigned __int64 Threadfunc, unsigned __int64 fakeAddr); };#include "XHideDll.h" #include "ntdll/ntdll.h" #include <TlHelp32.h> #include <intrin.h> #include <tchar.h> #include <stdio.h> #include <stdarg.h> #ifndef _WIN64 #pragma comment(lib,"ntdll/ntdll_x86.lib") #else #pragma comment(lib,"ntdll/ntdll_x64.lib") #endif #include <psapi.h> //#include <winternl.h> #pragma comment(lib, "Psapi.lib") #ifdef _DEBUG #define DbgPrint(_x_) Print _x_ #else #define DbgPrint(_x_) #endif typedef LPVOID WINAPI VIRTUALALLOC( _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect ); typedef void *MEMCPY( void *dest, const void *src, size_t count ); typedef bool UNLOADMODULE( HMODULE hModule, LPVOID lpNewBaseAddr, ULONG_PTR SizeOfImage ); typedef NTSTATUS WINAPI NTQUERYINFORMATIONPROCESS( _In_ HANDLE ProcessHandle, _In_ PROCESSINFOCLASS ProcessInformationClass, _Out_ PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength ); typedef struct _LDR_MODULE { LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; SHORT LoadCount; SHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, *PLDR_MODULE; //PsGetCurrentProcess(); void Print(const LPTSTR fmt, ...) { va_list args; va_start(args, fmt); int len = _vsctprintf(fmt, args); LPTSTR buffer = (LPTSTR)new TCHAR[len + 1]; _vsntprintf_s(buffer, len + 1, _TRUNCATE, fmt, args); va_end(args); OutputDebugString(buffer); delete[]buffer; } //bool IncModuleRefCount(HMODULE hModule) { // HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); // if (hSnapShot == INVALID_HANDLE_VALUE) { // DbgPrint((TEXT("CreateToolhelp32Snapshot failed, Error: %d\n"), GetLastError())); // return false; // } // // MODULEENTRY32 ModuleEntry = { 0 }; // ModuleEntry.dwSize = sizeof(MODULEENTRY32); // if (!Module32First(hSnapShot, &ModuleEntry)) { // DbgPrint((TEXT("Module32First failed, Error: %d\n"), GetLastError())); // CloseHandle(hSnapShot); // return false; // } // // do { // if (ModuleEntry.hModule != hModule) // LoadLibrary(ModuleEntry.szModule); // } while (Module32Next(hSnapShot, &ModuleEntry)); // // CloseHandle(hSnapShot); // return true; //} bool SetThreadsState(bool IsResume) { HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hSnapShot == INVALID_HANDLE_VALUE) { DbgPrint(((LPSTR)("CreateToolhelp32Snapshot failed, Error: %d\n"), GetLastError())); return false; } THREADENTRY32 ThreadEntry = { 0 }; ThreadEntry.dwSize = sizeof(ThreadEntry); DWORD ThreadId = GetCurrentThreadId(); DWORD ProcessId = GetCurrentProcessId(); if (!Thread32First(hSnapShot, &ThreadEntry)) { DbgPrint(((LPSTR)("Thread32First failed, Error: %d\n"), GetLastError())); CloseHandle(hSnapShot); return false; } do { if (ProcessId == ThreadEntry.th32OwnerProcessID && ThreadId != ThreadEntry.th32ThreadID) { HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, ThreadEntry.th32ThreadID); if (hThread != NULL) { if (IsResume) { ResumeThread(hThread); } else { SuspendThread(hThread); } CloseHandle(hThread); } } } while (Thread32Next(hSnapShot, &ThreadEntry)); CloseHandle(hSnapShot); return true; } void PrintModulesInformation() { NTQUERYINFORMATIONPROCESS *pfnNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS *)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationProcess"); if (pfnNtQueryInformationProcess != NULL) { PROCESS_BASIC_INFORMATION PBI = { 0 }; DWORD ReturnLength = 0; if (NT_SUCCESS(pfnNtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &PBI, sizeof(PBI), &ReturnLength))) { PLDR_MODULE LdrModule = NULL; PLIST_ENTRY Head = PBI.PebBaseAddress->Ldr->InMemoryOrderModuleList.Flink; PLIST_ENTRY Current = Head; do { LdrModule = (PLDR_MODULE)CONTAINING_RECORD(Current, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); Print((LPSTR)("Name: %s, BaseAddress: %p, LoadCount: %d\n"), LdrModule->BaseDllName.Buffer, LdrModule->BaseAddress, LdrModule->LoadCount); Current = Current->Flink; } while (Current != Head); } } } void AdjustModuleReferenceCount(HMODULE hModule) { NTQUERYINFORMATIONPROCESS *pfnNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS *)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationProcess"); if (pfnNtQueryInformationProcess != NULL) { PROCESS_BASIC_INFORMATION PBI = { 0 }; DWORD ReturnLength = 0; if (NT_SUCCESS(pfnNtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &PBI, sizeof(PBI), &ReturnLength))) { PLDR_MODULE LdrModule = NULL; PLIST_ENTRY Head = PBI.PebBaseAddress->Ldr->InMemoryOrderModuleList.Flink; PLIST_ENTRY Current = Head; do { LdrModule = (PLDR_MODULE)CONTAINING_RECORD(Current, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); // The LoadCount of DLLs that are static linked is -1, that kind of DLLs can't be freed // by FreeLibrary. So I modify that LoadCount to 1 in case that this DLL is static linked. if (LdrModule->BaseAddress == hModule) { // Add the reference count of DLLs that this module relies on LoadLibraryW(LdrModule->BaseDllName.Buffer); LdrModule->LoadCount = 1; break; } Current = Current->Flink; } while (Current != Head); } } } bool UnloadModule(HMODULE hModule, LPVOID lpNewBaseAddr, ULONG_PTR SizeOfImage) { LPVOID lpBaseOfDll = (LPVOID)hModule; MEMCPY *pfnMemCpy = (MEMCPY *)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "memcpy"); VIRTUALALLOC *pfnVirtualAlloc = (VIRTUALALLOC *)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "VirtualAlloc"); bool ret = false; if (!FreeLibrary(hModule)) { DbgPrint(((LPSTR)("FreeLibrary for the module failed, Error: %d\n"), GetLastError())); } // After FreeLibrary, we can't use any functions whose addresses are not retrieved before. // And the strings are invalid also. LPVOID OriBaseAddr = pfnVirtualAlloc(lpBaseOfDll, SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (OriBaseAddr == NULL) { // DbgPrint((TEXT("pfnVirtualAlloc for OriBaseAddr failed, Error: %d\n"), GetLastError())); } else if (OriBaseAddr != lpBaseOfDll) { // DbgPrint((TEXT("OriBaseAddr is not equal to lpBaseOfDll\n"))); } else { pfnMemCpy(OriBaseAddr, lpNewBaseAddr, SizeOfImage); ret = true; } return ret; } XHideDll::XHideDll() { } XHideDll::~XHideDll() { } PVOID GetProcessPEB() { #ifdef _WIN64 return (PVOID)__readgsqword(0x60); #else return (PVOID)__readfsdword(0x30); #endif } bool XHideDll::HideInLoadOrderLinks(DWORD64 dllBase) { BOOL r = FALSE; PPEB peb = (PPEB)GetProcessPEB(); PLIST_ENTRY OrderModuleHead, OrderModuleTail; PLDR_DATA_TABLE_ENTRY pLdrDataEntry = NULL; OrderModuleHead = OrderModuleTail = peb->Ldr->InLoadOrderModuleList.Blink; do { pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(OrderModuleHead, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (pLdrDataEntry->DllBase == NULL) { break; } // _tprintf(_T("DLLBase:%p Size:%08X ShortName:%s FullName:%s\n\n"), pLdrDataEntry->DllBase, pLdrDataEntry->SizeOfImage, // pLdrDataEntry->BaseDllName.Buffer, pLdrDataEntry->FullDllName.Buffer); if (pLdrDataEntry->DllBase == (HMODULE)dllBase) { RemoveEntryList(OrderModuleHead); r = TRUE; } OrderModuleHead = OrderModuleHead->Blink; } while (OrderModuleHead != OrderModuleTail); // _tprintf(_T("--------------------------------------------------------------\n")); return r; } bool XHideDll::HideInMemoryOrderLinks(DWORD64 dllBase) { BOOL r = FALSE; PPEB peb = (PPEB)GetProcessPEB(); PLIST_ENTRY OrderModuleHead, OrderModuleTail; PLDR_DATA_TABLE_ENTRY pLdrDataEntry = NULL; OrderModuleHead = OrderModuleTail = peb->Ldr->InMemoryOrderModuleList.Blink; do { pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(OrderModuleHead, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pLdrDataEntry->DllBase == NULL) { break; } // _tprintf(_T("DLLBase:%p Size:%08X ShortName:%s FullName:%s\n\n"), pLdrDataEntry->DllBase, pLdrDataEntry->SizeOfImage, // pLdrDataEntry->BaseDllName.Buffer, pLdrDataEntry->FullDllName.Buffer); if (pLdrDataEntry->DllBase == (HMODULE)dllBase) { RemoveEntryList(OrderModuleHead); r = TRUE; } OrderModuleHead = OrderModuleHead->Blink; } while (OrderModuleHead != OrderModuleTail); // _tprintf(_T("--------------------------------------------------------------\n")); return r; } bool XHideDll::HideInInitializationOrderLinks(DWORD64 dllBase) { bool r = FALSE; PPEB peb = (PPEB)GetProcessPEB(); PLIST_ENTRY OrderModuleHead, OrderModuleTail; PLDR_DATA_TABLE_ENTRY pLdrDataEntry = NULL; OrderModuleHead = OrderModuleTail = peb->Ldr->InInitializationOrderModuleList.Blink; do { pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(OrderModuleHead, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks); if (pLdrDataEntry->DllBase == NULL) { break; } // _tprintf(_T("DLLBase:%p Size:%08X ShortName:%s FullName:%s\n\n"), pLdrDataEntry->DllBase, pLdrDataEntry->SizeOfImage, // pLdrDataEntry->BaseDllName.Buffer, pLdrDataEntry->FullDllName.Buffer); if (pLdrDataEntry->DllBase == (HMODULE)dllBase) { RemoveEntryList(OrderModuleHead); r = TRUE; } OrderModuleHead = OrderModuleHead->Blink; } while (OrderModuleHead != OrderModuleTail); // //_tprintf(_T("--------------------------------------------------------------\n")); return r; } void XHideDll::PrintModuleList() { HANDLE hSnaphot; MODULEENTRY32 md32 = { 0 }; BOOL exist; md32.dwSize = sizeof(md32); hSnaphot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); exist = Module32First(hSnaphot, &md32); while (exist) { //_tprintf(_T("%s\n"), md32.szModule); exist = Module32Next(hSnaphot, &md32); } CloseHandle(hSnaphot); //_tprintf(_T("--------------------------------------------")); } void XHideDll::HideModule(unsigned long long hModule1, bool DeleteAfter) { MODULEINFO ModuleInfo = { 0 }; HMODULE hModule = (HMODULE)hModule1; if (!GetModuleInformation(GetCurrentProcess(), hModule, &ModuleInfo, sizeof(ModuleInfo))) { DbgPrint(((LPSTR)("GetModuleInformation0 failed, Error: %d\n"), GetLastError())); return; } LPVOID lpNewBaseAddr = VirtualAlloc(NULL, ModuleInfo.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (lpNewBaseAddr == NULL) { DbgPrint(((LPSTR)("VirtualAlloc for lpNewBaseAddr failed, Error: %d\n"), GetLastError())); return; } memcpy(lpNewBaseAddr, ModuleInfo.lpBaseOfDll, ModuleInfo.SizeOfImage); UNLOADMODULE *pfnUnloadModule = (UNLOADMODULE *)((ULONG_PTR)UnloadModule - (ULONG_PTR)ModuleInfo.lpBaseOfDll + (ULONG_PTR)lpNewBaseAddr); /*Print((LPSTR)("\n----------------------------------------------------------------------------\n")); Print((LPSTR)("Check the modules before adjusting the reference count of the loaded modules\n")); PrintModulesInformation();*/ AdjustModuleReferenceCount(hModule); /*Print((LPSTR)("\n---------------------------------------------------------------------------\n")); Print((LPSTR)("Check the modules after adjusting the reference count of the loaded modules\n")); PrintModulesInformation();*/ TCHAR FileName[MAX_PATH] = { 0 }; bool HasFileName = false; if (DeleteAfter) { if (!GetModuleFileName(hModule, FileName, _countof(FileName))) { DbgPrint(((LPSTR)("GetModuleFileName failed, Error: %d\n"), GetLastError())); } else { HasFileName = true; } } SetThreadsState(false); // Jump to the new space, and free the original dll in the new space if (!pfnUnloadModule(hModule, lpNewBaseAddr, ModuleInfo.SizeOfImage)) { DbgPrint(((LPSTR)("UnloadModule failed, Error: %d\n"), GetLastError())); } // Jump back to the original space SetThreadsState(true); //Print((LPSTR)("\n---------------------------------------------\n")); //Print((LPSTR)("Check the modules after FreeLibrary is called\n")); //PrintModulesInformation(); if (!VirtualFree(lpNewBaseAddr, 0, MEM_DECOMMIT)) { DbgPrint(((LPSTR)("VirtualFree for lpNewBaseAddr failed, Error: %d\n"), GetLastError())); } if (HasFileName) { if (!DeleteFile(FileName)) { DbgPrint(((LPSTR)("DeleteFile failed, Error: %d\n"), GetLastError())); } } } void XHideDll::LM_CreateFakeThread(unsigned __int64 Threadfunc, unsigned __int64 fakeAddr) { CONTEXT ctx; HANDLE tHand = CreateRemoteThread(GetCurrentProcess(), 0, 0, (LPTHREAD_START_ROUTINE)fakeAddr, 0, 0, 0); SuspendThread(tHand); ctx.ContextFlags = CONTEXT_INTEGER; GetThreadContext(tHand, &ctx); ctx.Rax = Threadfunc; ctx.ContextFlags = CONTEXT_INTEGER; SetThreadContext(tHand, &ctx); ResumeThread(tHand); }二、utils工具函数#pragma once #include <string> #include <windows.h> namespace utils { void log(char* _format, ...);// void log(const char* _format, ...); template<class T> T read(const DWORD64& obj) { T value = { 0 }; __try { value = *(T*)(obj); } __except (1) { log("read ptr:%I64x error!", obj); } return value; } template<class T> bool write(const DWORD64& obj, T value) { __try { *(T*)(obj) = value; return true; } __except (1) { log("write ptr:%I64x error!", obj); return false; } } //ascii 文本转 utf-8文本 int mb2u8(const char* cont, char* lpsz); //utf-8文本转ascii文本 int u82mb(const char* cont, char* lpsz); //64位指针转文本 std::string ptr2str(DWORD64 ptr); void StrToHex(char* pbDest, char* pszSrc, int nLen); //十六进制转字符串 void HexToStr(char* pszDest,unsigned char* pbSrc, int nLen); //获取进程指定窗口句柄 HWND GetWindowHwndByPorcessID(DWORD dwProcessID, char* className, char* windowName); } #include "Utils.h" void utils::log(char* _format, ...) { #ifdef _DEBUG std::string temp; va_list marker = { 0 }; va_start(marker, _format); size_t num_of_chars = _vscprintf(_format, marker); if (num_of_chars > temp.capacity()) { temp.reserve(num_of_chars + 1); } vsprintf_s(const_cast<char*>(temp.c_str()), num_of_chars + 1, _format, marker); OutputDebugString(temp.c_str()); #endif } void utils::log(const char* _format, ...) { #ifdef _DEBUG std::string temp; va_list marker = { 0 }; va_start(marker, _format); size_t num_of_chars = _vscprintf(_format, marker); if (num_of_chars > temp.capacity()) { temp.reserve(num_of_chars + 1); } vsprintf_s(const_cast<char*>(temp.c_str()), num_of_chars + 1, _format, marker); OutputDebugString(temp.c_str()); #endif } int utils::mb2u8(const char* cont, char* lpsz) { if (NULL == cont || lpsz == NULL) { return 0; } int num = MultiByteToWideChar(CP_ACP, NULL, cont, -1, NULL, NULL); if (num <= 0) { return 0; } wchar_t* buffw = new (std::nothrow) wchar_t[num + 1]; if (NULL == buffw) { return 0; } MultiByteToWideChar(CP_ACP, NULL, cont, -1, buffw, num); int len = WideCharToMultiByte(CP_UTF8, 0, buffw, num - 1, NULL, NULL, NULL, NULL); if (len <= 0) { delete[] buffw; return 0; } //lpsz = new (std::nothrow) char[len + 1]; //ZeroMemory(lpsz, len + 1); if (NULL == lpsz) { delete[] buffw; return 0; } WideCharToMultiByte(CP_UTF8, 0, buffw, num - 1, lpsz, len, NULL, NULL); lpsz[len] = '\0'; delete[] buffw; return len + 1; } int utils::u82mb(const char* cont, char* lpsz) { int iLen = 0; if (NULL == cont || lpsz == nullptr) { return iLen; } int num = MultiByteToWideChar(CP_UTF8, NULL, cont, -1, NULL, NULL); if (num <= 0) { return iLen; } wchar_t* buffw = new (std::nothrow) wchar_t[num]; if (NULL == buffw) { return iLen; } MultiByteToWideChar(CP_UTF8, NULL, cont, -1, buffw, num); int len = WideCharToMultiByte(CP_ACP, 0, buffw, num - 1, NULL, NULL, NULL, NULL); if (len <= 0) { delete[] buffw; return iLen; } //char* lpsz = new (std::nothrow) char[len + 1]; if (NULL == lpsz) { delete[] buffw; return iLen; } ZeroMemory(lpsz, len + 1); WideCharToMultiByte(CP_ACP, 0, buffw, num - 1, lpsz, len, NULL, NULL); lpsz[len] = '\0'; delete[] buffw; /*std::string rtn(lpsz); delete[] lpsz;*/ return len; } std::string utils::ptr2str(DWORD64 ptr) { char tmp[256] = "\0"; sprintf_s(tmp, "%I64x", ptr); return std::string(tmp); } void utils::StrToHex(char* pbDest, char* pszSrc, int nLen) { char h1, h2; char s1, s2; for (int i = 0; i < nLen; i++) { h1 = pszSrc[2 * i]; h2 = pszSrc[2 * i + 1]; s1 = toupper(h1) - 0x30; if (s1 > 9) s1 -= 7; s2 = toupper(h2) - 0x30; if (s2 > 9) s2 -= 7; pbDest[i] = s1 * 16 + s2; } } void utils::HexToStr(char* pszDest, unsigned char* pbSrc, int nLen) { char ddl, ddh; for (int i = 0; i < nLen; i++) { ddh = 48 + pbSrc[i] / 16; ddl = 48 + pbSrc[i] % 16; if (ddh > 57) ddh = ddh + 7; if (ddl > 57) ddl = ddl + 7; pszDest[i * 3] = ddh; pszDest[i * 3 + 1] = ddl; pszDest[i * 3 + 2] = 0x20; } } HWND utils::GetWindowHwndByPorcessID(DWORD dwProcessID, char* className, char* windowName) { HWND tempH = NULL; do { tempH = ::FindWindowExA(NULL, tempH, className, windowName); DWORD pid; ::GetWindowThreadProcessId(tempH, &pid); if (pid == dwProcessID) { return tempH; } } while (tempH != NULL); return NULL; }三、对象遍历该游戏对象存储使用单链链表存储，因此，其遍历如下所示:// TODO: 在此添加控件通知处理程序代码 auto ptr = utils::read<DWORD64>(g_ModuleHandle + offset::CurMgrPointer); std::string str = "ptr:" + utils::ptr2str(ptr)+"\r\n"; ptr = utils::read<DWORD64>(ptr + offset::FirstObject); int index = 0; while (ptr != 0 && ptr % 2 != 1 && index<500) { //auto objptr = ptr + offset::FirstObject; str += "obj：" + utils::ptr2str(ptr); auto type = utils::read<UINT8>(ptr + offset::ObjectType); str += "\ttype：" + std::to_string(type); DWORD64 namePtr = 0; if (type == 3) //unit { namePtr = utils::read<DWORD64>(ptr + offset::UnitName1); namePtr = utils::read<DWORD64>(namePtr + offset::UnitName2); float x=0.0, y = 0.0, z = 0.0, rotation = 0.0, pitch = 0.0; x = utils::read<float>(ptr + offset::UnitX); y = utils::read<float>(ptr + offset::UnitX+4); z = utils::read<float>(ptr + offset::UnitX+0x8); rotation = utils::read<float>(ptr + offset::UnitX+0x10); pitch = utils::read<float>(ptr + offset::UnitX+0x14); str += "\t" + std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z) + ":" + std::to_string(rotation) + "-" + std::to_string(pitch); } else if (type == 6) //Object { namePtr = utils::read<DWORD64>(ptr + offset::ObjectName1); namePtr = utils::read<DWORD64>(namePtr + offset::ObjectName2); float x = 0.0, y = 0.0, z = 0.0, rotation = 0.0, pitch = 0.0; x = utils::read<float>(ptr + offset::ObjectX); y = utils::read<float>(ptr + offset::ObjectX + 4); z = utils::read<float>(ptr + offset::ObjectX + 0x8); rotation = utils::read<float>(ptr + offset::ObjectX + 0x10); str +="\t"+ std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z) + ":" + std::to_string(rotation); } if (namePtr != 0) { char tmp[250] = ""; utils::u82mb((const char*)namePtr, tmp); str += "\t name：" + std::string(tmp) ; } else { str += "\t name："; } //hp&lv auto descriptor = utils::read<DWORD64>(ptr + offset::UnitDescript); DWORD hp = 0, lv = 0; hp = utils::read<DWORD>(descriptor + offset::UnitHp); lv = utils::read<DWORD>(descriptor + offset::UnitLv); str += "\thp：" + std::to_string(hp) + "\tlv："+std::to_string(lv); //localGUID char guid[64] = "\0"; utils::HexToStr(guid,(unsigned char*) (ptr + offset::LocalGUID), 16); str += "\tguid：" + std::string(guid) + "\r\n"; ptr = utils::read<DWORD64>(ptr + offset::NextObject); index++; } SetDlgItemText(IDC_EDIT1, str.c_str());其对象类型枚举类如下：enum ObjType : uint8_t { Object = 0, Item = 1, Container = 2, Unit = 3, Player = 4, ActivePlayer = 5, GameObject = 6, DynamicObject = 7, Corpse = 8, AreaTrigger = 9, SceneObject = 10, Conversation = 11, AIGroup = 12, Scenario = 13, Loot = 14, Invalid }四、主线程调用采用窗口子类化的方式实现，其他方式可参考：APC插入、dxd endscen hook、虚表hook等。//窗口子类化 g_oldProcFunc = GetWindowLongPtr(g_hwnd, -4); SetWindowLongPtrA(g_hwnd, -4, (LONG_PTR)MyCallBack);//如果需要安全卸载DLL，退出处需要将处理函数恢复。 LRESULT MyCallBack(HWND h, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case MSG_LUA_RUN: //Frame_ExcuteBuffer { break; } case MSG_PLAYER_RUN: //clickTo { break; } case MSG_LUA_RET: //Frame_GetText { break; } case MSG_LUA_REGISTER: { //Frame_RigisterFunction break; } default: break; } return CallWindowProc((WNDPROC)g_oldProcFunc, h, msg, wParam, lParam); }五、lua脚本执行处理MSG_LUA_RUN消息：DWORD64 ptr = 0; if (utils::read<char>((DWORD64)lParam) != '\0') { utils::log("MSG_LUA_RUN:%s", (char*)lParam); __try { g_Frame_ExcuteScript((DWORD64)lParam, (DWORD64)lParam, 0); } __except (1) //接收处理 游戏 retaddr 不通过后产生的异常 { utils::log("g_Frame_ExcuteScript error:%s", (char*)lParam); } } else { utils::log((char*)"******* lua run error *******"); } }调用：// TODO: 在此添加控件通知处理程序代码 CString str; GetDlgItemText(IDC_EDIT5, str); _call_lua_without_ret(str.GetBuffer()); str.ReleaseBuffer(); void _call_lua_without_ret(const char* str) { char tmpUtf[LUA_COMMAND_MAX_LENGTH] = { 0 }; int iLen = utils::mb2u8(str, tmpUtf); DWORD dwMemPro = NULL; if (iLen <= 0) return; ::SendMessage(g_hwnd, MSG_LUA_RUN, 0, (LPARAM)tmpUtf); } 六、lua返回值获取处理MSG_LUA_RET消息：DWORD64 rdx = -1; char c[250] = "retn"; char* temp_utf_8 = NULL; if (*(char*)lParam != '\0') //*(char*)lParam!='\0' lizi { __try { g_Frame_ExcuteScript((DWORD64)lParam, (DWORD64)lParam, 0);//脚本执行 } __except (1) { utils::log("g_Frame_ExcuteScript error:%s",(char*) lParam); } } else { utils::log((char*)"******* lua run error *******"); break; } if (*(char*)lParam != '\0' && c != 0 && rdx != 0) { __try { temp_utf_8 = (char*)g_Frame_GetText((DWORD64)c, rdx, 0, 0);//获取返回值 } __except (1) { utils::log("g_Frame_GetText error:%s", (char*)lParam); } } else { utils::log((char*)"******My_lua_runret_func error ********"); break; } if (temp_utf_8) { ZeroMemory(g_RetText, 1000); utils::u82mb(temp_utf_8, g_RetText); //编码转换为ascii } else { utils::log((char*)"******temp_utf_8 is null ********"); } 调用：CString str; GetDlgItemText(IDC_EDIT6, str); char tmpUtf[LUA_COMMAND_MAX_LENGTH] = { 0 }; char tmpcommand[LUA_COMMAND_MAX_LENGTH] = { 0 }; sprintf_s(tmpUtf, "retn=%s", str.GetBuffer()); //utils::log(tmpUtf); int iLen = utils::mb2u8(tmpUtf, tmpcommand); if (iLen <= 0) return ; ::SendMessage(g_hwnd, MSG_LUA_RET, 0, (LPARAM)tmpcommand); str.ReleaseBuffer(); SetDlgItemText(IDC_EDIT9, g_RetText);七、角色移动处理消息：Position* targetpos = (Position*)lParam; DWORD64 playerAddr = getLocalPlayerPtr(); //获取本地玩家指针 if (targetpos != 0 && playerAddr != 0) { g_Func_ClickTo(playerAddr, (DWORD64)targetpos, 0); //点击移动 } else { utils::log((char*)"******My_lua_pyrun_func error ********"); }调用：CString strx, stry, strz; GetDlgItemText(IDC_EDIT2, strx); GetDlgItemText(IDC_EDIT3, stry); GetDlgItemText(IDC_EDIT4, strz); float x = std::atof(strx.GetBuffer()); float y = std::atof(stry.GetBuffer()); float z = std::atof(strz.GetBuffer()); strx.ReleaseBuffer(); stry.ReleaseBuffer(); strz.ReleaseBuffer(); Position p{ x,y,z }; ::SendMessage(g_hwnd, MSG_PLAYER_RUN, 0, (LPARAM)&p);八、登陆这里就很简单了，测试了个账号、密码输入：_call_lua_without_ret("AccountLogin.UI.PasswordEditBox:SetText(\'*****\')");//输入密码 Sleep(1000); _call_lua_without_ret("C_Login.Login(\'*****\', AccountLogin.UI.PasswordEditBox)");//输入账号并登陆九、下一步工作对各功能进行封装，实现lua脚本调用。
某游戏自动化脚本系列 （五）注入带MFC窗口的Dll
刘某人重新回来啦~带MFC窗口，以更方便数据遍历和功能测试。一、带MFC的Dll使用VS2019 创建带MFC的动态链接库（DLL），项目属性设置使用静态MFC。新建名为DbgWin的对话框界面。在DLL工程名cpp中（比如我DLL工程名叫GameCore，则目标文件为 GameCore.cpp）添加头文件 DbgWin.h 引用。并添加如下代码：DbgWin* m_dbgWin; DWORD WINAPI ShowDialog(LPARAM lp) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); //MFC宏，必须，确保资源正确加载 m_dbgWin = new DbgWin; m_dbgWin->DoModal(); //以模态方式创建窗口 m_dbgWin->ShowWindow(SW_SHOWNORMAL); //显示窗口，可hook消息处理函数，通过按键显示、隐藏窗口 return 0; }修改BOOL CGameCoreApp::InitInstance()函数为：BOOL CGameCoreApp::InitInstance() { CWinApp::InitInstance(); ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ShowDialog, NULL, NULL, NULL); return TRUE; }设计如下MFC界面：二、DLL注入#include <iostream> #include <Windows.h> #include <string.h> #include <thread> #include <chrono> int main() { char* commandLineCopy = NULL; std::string tmpString = "D:\\Program Files (x86)\\｛此处隐藏｝.exe"; commandLineCopy = (char*)tmpString.c_str(); int cdSize = strlen(commandLineCopy) + 1; // 创建进程并暂停 LPSTARTUPINFOA startInfo = new STARTUPINFOA(); PROCESS_INFORMATION processInfo = { 0 }; ZeroMemory(startInfo, sizeof(STARTUPINFOA)); startInfo->cb = sizeof(STARTUPINFOA); if (!CreateProcessA(NULL, (LPSTR)"D:\\Program Files (x86)\\｛此处隐藏｝.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, "D:\\Program Files (x86)\\｛此处隐藏｝", startInfo, &processInfo)) { delete startInfo; return false; } auto pid = processInfo.dwProcessId; //打开进程，获取进程句柄 HANDLE targetProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (targetProc == NULL) { return false; } std::string tmpChar = "H:\\2Project\\testMFCLibrary1\\x64\\Debug\\GameCore.dll"; char* pChar = (char*)tmpChar.c_str(); int dllLen = strlen(pChar) + 1; // 1.目标进程申请空间 LPVOID pDLLPath = VirtualAllocEx(targetProc, NULL, dllLen, MEM_COMMIT, PAGE_READWRITE); if (pDLLPath == NULL) { //qDebug() << "VirtualAllocEx error"; return false; } SIZE_T wLen = 0; // 2.将DLL路径写进目标进程内存空间 int ret = WriteProcessMemory(targetProc, pDLLPath, pChar, dllLen, &wLen); if (ret == 0) { //qDebug() << "WriteProcessMemory error"; return false; } // 3.获取LoadLibraryA函数地址 FARPROC myLoadLibrary = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); if (myLoadLibrary == NULL) { //qDebug() << "GetProcAddress error"; return false; } // 4.在目标进程执行LoadLibrary 注入指定的线程 HANDLE tHandle = CreateRemoteThread(targetProc, NULL, NULL, (LPTHREAD_START_ROUTINE)myLoadLibrary, pDLLPath, NULL, NULL); if (tHandle == NULL) { //qDebug() << "CreateRemoteThread error"; return false; } WaitForSingleObject(tHandle, INFINITY); // 恢复线程 ResumeThread(processInfo.hThread); CloseHandle(tHandle); CloseHandle(targetProc); delete startInfo; }三、下一步工作DLL隐藏安全内存读写及主线程调用call实现
某游戏自动化脚本系列（四）第二期 PE节保护
某游戏使用了PE节保护技术,重新映射PE内存,致使PE节不可修改参考资料：1.[[原创]分享下wow PE section保护技术](https://bbs.pediy.com/thread-268057.htm)2.Self-Remapping-Code参考资料1中，是使用lua来实现的内层空间重新映射，自己对这块关注蛮久了，遂使用c/c++进行了实现。说明：本代码运行于R3层，无需进行驱动开发。一.结构体定义typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING; typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor; PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT, * PSECTION_INHERIT;二.ntdll未导出函数声明及地址获取1.函数声明typedef DWORD(WINAPI* NtSuspendProcess)(HANDLE ProcessHandle); //进程暂停 typedef DWORD(WINAPI* NtResumeProcess)(HANDLE hProcess); //进程恢复 typedef NTSTATUS(NTAPI* NtCreateSection)( _Out_ PHANDLE SectionHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PLARGE_INTEGER MaximumSize, _In_ ULONG SectionPageProtection, _In_ ULONG AllocationAttributes, _In_opt_ HANDLE FileHandle ); typedef NTSTATUS (NTAPI* NtMapViewOfSection)( _In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, _Inout_ PVOID* BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, _In_ SECTION_INHERIT InheritDisposition, _In_ ULONG AllocationType, _In_ ULONG Win32Protect ); typedef NTSTATUS(NTAPI* NtUnmapViewOfSection)( _In_ HANDLE ProcessHandle, _In_opt_ PVOID BaseAddress ); typedef NTSTATUS(NTAPI* NtClose)( _In_ HANDLE Handle );2.函数地址获取NtSuspendProcess m_NtSuspendProcess; NtResumeProcess m_NtResumeProcess; NtCreateSection m_NtCreateSection; NtMapViewOfSection m_NtMapViewOfSection; NtUnmapViewOfSection m_NtUnmapViewOfSection; NtClose m_NtClose; HMODULE h_module = LoadLibrary("ntdll.dll"); if (h_module == 0) { printf("loadlibrary error.\n"); return 0; } m_NtResumeProcess = (NtResumeProcess)GetProcAddress(h_module, "NtResumeProcess"); m_NtSuspendProcess = (NtResumeProcess)GetProcAddress(h_module, "NtSuspendProcess"); m_NtCreateSection = (NtCreateSection)GetProcAddress(h_module, "NtCreateSection"); m_NtMapViewOfSection=(NtMapViewOfSection)GetProcAddress(h_module, "NtMapViewOfSection"); m_NtUnmapViewOfSection=(NtUnmapViewOfSection)GetProcAddress(h_module, "NtUnmapViewOfSection"); m_NtClose=(NtClose)GetProcAddress(h_module, "NtClose");3.核心功能函数实现bool RemapViewOfSection(HANDLE handle, DWORD64 baseAddress, DWORD64 regionSize, DWORD NewProtection) { // 申请内存空间以存放目标节内容 auto address = VirtualAlloc(NULL, regionSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (address == 0) { printf("virtualalloc error.\n"); return false; } SIZE_T numberOfBytesRead = 0; //读取目标内存 if (!ReadProcessMemory(handle, (LPCVOID)baseAddress, address, regionSize, &numberOfBytesRead)) { printf("ReadProcessMemory error.\n"); return false; } HANDLE hSection; LARGE_INTEGER cbSectionSize = {}; cbSectionSize.QuadPart = regionSize; //重新申请节 auto ntStatus = m_NtCreateSection(&hSection,SECTION_ALL_ACCESS,NULL,&cbSectionSize,PAGE_EXECUTE_READWRITE,SEC_COMMIT,NULL); if (ntStatus != STATUS_SUCCESS) { printf("NtCreateSection error.\n"); return false; } //释放目标节 if (m_NtUnmapViewOfSection(handle, (LPVOID)baseAddress) != STATUS_SUCCESS) { printf("NtUnmapViewOfSection error.\n"); return false; } SIZE_T cbViewSize = 0; cbSectionSize.QuadPart = 0; //重新映射，注意这里需要将cbSectionSize.QuadPart 置0。 ntStatus = m_NtMapViewOfSection(hSection, handle, (PVOID*)&baseAddress, 0, regionSize, &cbSectionSize, &cbViewSize, ViewUnmap, 0, NewProtection); if (ntStatus != STATUS_SUCCESS) { printf("NtMapViewOfSection error.\n"); return false; } //目标内存数据恢复 if (!WriteProcessMemory(handle, (LPVOID)baseAddress, address, regionSize, &numberOfBytesRead)) { printf("WriteProcessMemory error.\n"); return false; } VirtualFree(address, regionSize, MEM_RELEASE); return true; }