Loading... 随着某雪在应用层(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相关结构体定义** ```c++ #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; } ``` **未整理的结构体定义** ```c -#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 @@ ``` # 三、世界坐标与屏幕坐标转换 具体细节可参考[详细的世界坐标转屏幕坐标及正交投影矩阵的推导](https://zhuanlan.zhihu.com/p/63275902) ```c++ 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实现鼠标左键点击,然后我就分析了一下。) 通过对游戏的分析,发现游戏并不存在特殊控件,具体原因可由如下代码简单发现: ```c++ 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.”,查看其调用链,定位到如下函数: ```c++ __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)`个函数。 通过虚函数表定位到对应函数: ```c++ __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; } ``` 于是构造如下函数,注入主线程执行,测试一切正合我意!! ```c++ 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); //参数 } ``` 完美解决。 # 五、抱歉 不出意外的话,本系列文章提前终止了,实在抱歉。 最后修改:2021 年 09 月 22 日 11 : 36 AM © 禁止转载
2 条评论
一口气看完,过瘾,大佬牛逼。|´・ω・)ノ