逆向分析Lua注入类型外挂

17k 词
        <h2 id="相关"><a href="#相关" class="headerlink" title="相关"></a>相关</h2><p>该网游类型为横版过关类角色扮演游戏,之前在这上面花了不少时间。刚接触逆向分析,所以比较好奇外挂以及游戏漏洞的原理,故写此记录一下分析过程。  </p>

找寻样本

该游戏近年在国内市场效应不佳,所以游戏工作室以及外挂制作等业务逐渐退出国服范围。在谷歌搜索后,发现存在许多类型为Lua 注入的外挂–也就是向游戏注入Lua脚本代码,执行实现非法行为的手段。而其中支持国服的,只有一款,下载后对其进行分析。

分析

下载下来后,发现该外挂为单文件,对其进行查壳操作。

为.NET程序,并且无壳,这就相当于开源了。使用Reflector反编译:

无混淆,直接导出源码,丢入vs2017,方便阅读代码。

加载器目录结构:

加载器功能函数:

加载器主要逻辑

WPF程序启动函数:

        private void Application_Startup(object sender, System.Windows.StartupEventArgs e)  
        {  
            this.DeleteOld();  
            this.CreateNew();  
            Environment.Exit(0);  
        }  
    private void CreateNew()  
    {  
        string location = Assembly.GetExecutingAssembly().Location;  
        string directoryName = MyWpfExtension.Computer.FileSystem.GetFileInfo(location).DirectoryName;  
        byte[] lunaLoader = LunaLoader_Launcher.My.Resources.Resources.LunaLoader;  
        string right = Functions.Random(5).ToString().ToLower();  
        object obj2 = Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(this.localDirectory, @&#34;&#34;), right), &#34;.exe&#34;);  
        Functions.RegistryWrite(&#34;LastExecutable_Path&#34;, Conversions.ToString(obj2));  
        try  
        {  
            File.WriteAllBytes(Conversions.ToString(obj2), lunaLoader);  
        }  
        catch (Exception exception1)  
        {  
            ProjectData.SetProjectError(exception1);  
            Functions.Message(&#34;Can&#39;t write new program files.&#34;, Conversions.ToString(3));  
            ProjectData.ClearProjectError();  
        }  
        try  
        {  
            Process.Start(right, &#34;&#34;&#34; + directoryName + &#34;&#34;&#34;);  
        }  
        catch (Exception exception2)  
        {  
            ProjectData.SetProjectError(exception2);  
            Functions.Message(&#34;The application files are corrupted, please disable your anti-virus.&#34;, Conversions.ToString(3));  
            ProjectData.ClearProjectError();  
        }  
    }  

    private void DeleteOld()  
    {  
        object obj2 = Functions.RegistryRead(&#34;LastExecutable_Path&#34;);  
        try  
        {  
            if (File.Exists(Conversions.ToString(obj2)))  
            {  
                File.Delete(Conversions.ToString(obj2));  
            }  
        }  
        catch (Exception exception1)  
        {  
            ProjectData.SetProjectError(exception1);  
            Functions.Message(&#34;Can&#39;t delete old program files.&#34;, Conversions.ToString(3));  
            ProjectData.ClearProjectError();  
        }  
    }  

整体逻辑非常清晰,删除上一条注册表LastExecutable_Path (SoftwareLunaLoader)的值,随机生成文件名,然后向其写入新文件名,然后在Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)目录中生成程序内的压缩资源。可以肯定此单文件程序为主程序释放器。让我们对释放出来的资源进行分析,由于释放后程序必须由启动器启动,为了调试方便我直接将程序源码修改,PATCH掉了随机文件名部分,指定固定的文件名,方便启动以及调试。

登录器分析

首先先查询壳信息

显示为ConfuserEx(1.0.0)[-]

丢入反编译工具中,发现特征,以及字符串等信息都被混淆了。
尝试带壳调试,但是整体堆栈调用太复杂了,并且通信使用了ssl,Hook了发包收包函数也无法获取信息,遂放弃。
此壳为魔改版,原版是开源的,所以原版脱壳工具都无法使用。硬实力还是弱了,拖不下来这个壳。
开始找寻旁路,对该进程进行行为监控。

发现其有多次写入文件以及注册表操作:

挨个分析,由于文件名也是随机生成的,我这里简单定义一下各个dll作用,方便辨识。

4pcl9.dll

功能函数,提供导出函数,为外挂的核心功能dll

amdd3drt.dll

无导出函数

猜测功能为DLL注入,查看DLLMain函数

BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)  
{  
  int v3; // eax  
  int v5; // [esp+4h] [ebp-20Ch]  
  CHAR Filename; // [esp+108h] [ebp-108h]  

if ( fdwReason == 1 && !dword_10019238 )
{
dword_10019238 = 1;
GetModuleFileNameA(0, &Filename, 0x104u);
sub_10003628(&Filename, 0, 0, (int)&v5, 0);
v3 = strcmp((const char *)&v5, (const char *)&unk_10016488);
if ( v3 )
v3 = -(v3 < 0) | 1;
if ( v3 )
return 0;
sub_10001010((int)"LunaLoader attached!n");
sub_10001010((int)"nnBy Joni-St, and Ferrums.");
dword_1001924C = (int)GetCurrentProcess();
dword_10019250 = (int)hinstDLL;
CreateThread(0, 0, StartAddress, &dword_1001924C, 0, 0);
}
return 1;
}

可以看到逻辑为,通过dll劫持后,比对进程名字是否为游戏进程,是的话就在游戏(自身)进程内存中创建新的进程并执行StartAddress函数。

StartAddress:
主要逻辑用注释标识了。

DWORD __stdcall StartAddress(LPVOID lpThreadParameter)  
{  
  HMODULE v2; // eax  
  HMODULE v3; // edi  
  FARPROC v4; // eax  
  FARPROC v5; // ebx  
  FARPROC v6; // eax  
  HANDLE v7; // eax  
  HANDLE v8; // ebx  
  char *v9; // esi  
  DWORD v10; // ecx  
  char v11; // ah  
  DWORD v12; // edx  
  char *v13; // edi  
  char v14; // al  
  char v15; // al  
  CHAR *v16; // edi  
  DWORD v17; // edi  
  int v18; // ecx  
  char *v19; // edx  
  LPCSTR v20; // ebx  
  CHAR *v21; // eax  
  CHAR *v22; // edi  
  CHAR *v23; // edi  
  char *v24; // eax  
  CHAR v25; // cl  
  const char *i; // ebx  
  LPCSTR v27; // edi  
  int v28; // eax  
  int *v29; // eax  
  int v30; // eax  
  int v31; // ecx  
  unsigned int v32; // edx  
  int v33; // ecx  
  void *v34; // eax  
  HANDLE v35; // edi  
  void *v36; // ebx  
  _DWORD *v37; // esi  
  int v38; // ST14_4  
  struct _SYSTEM_INFO SystemInfo; // [esp+0h] [ebp-106ACh]  
  struct _MEMORY_BASIC_INFORMATION Buffer; // [esp+24h] [ebp-10688h]  
  DWORD v41; // [esp+40h] [ebp-1066Ch]  
  DWORD v42; // [esp+44h] [ebp-10668h]  
  DWORD v43; // [esp+48h] [ebp-10664h]  
  LPCWSTR lpWideCharStr; // [esp+4Ch] [ebp-10660h]  
  HANDLE hFile; // [esp+50h] [ebp-1065Ch]  
  DWORD NumberOfBytesRead; // [esp+54h] [ebp-10658h]  
  HANDLE *v47; // [esp+58h] [ebp-10654h]  
  LPCSTR lpszVolumeMountPoint; // [esp+5Ch] [ebp-10650h]  
  int v49; // [esp+60h] [ebp-1064Ch]  
  char v50; // [esp+64h] [ebp-10648h]  
  __int16 v51; // [esp+390h] [ebp-1031Ch]  
  char v52; // [esp+10064h] [ebp-648h]  
  CHAR LibFileName; // [esp+10168h] [ebp-544h]  
  CHAR MultiByteStr; // [esp+1026Ch] [ebp-440h]  
  __int128 v55; // [esp+10370h] [ebp-33Ch]  
  __int128 v56; // [esp+10380h] [ebp-32Ch]  
  __int128 v57; // [esp+10390h] [ebp-31Ch]  
  __int128 v58; // [esp+103A0h] [ebp-30Ch]  
  __int128 v59; // [esp+103B0h] [ebp-2FCh]  
  __int128 v60; // [esp+103C0h] [ebp-2ECh]  
  __int128 v61; // [esp+103D0h] [ebp-2DCh]  
  __int128 v62; // [esp+103E0h] [ebp-2CCh]  
  __int128 v63; // [esp+103F0h] [ebp-2BCh]  
  __int128 v64; // [esp+10400h] [ebp-2ACh]  
  __int128 v65; // [esp+10410h] [ebp-29Ch]  
  __int128 v66; // [esp+10420h] [ebp-28Ch]  
  __int128 v67; // [esp+10430h] [ebp-27Ch]  
  __int128 v68; // [esp+10440h] [ebp-26Ch]  
  __int128 v69; // [esp+10450h] [ebp-25Ch]  
  __int128 v70; // [esp+10460h] [ebp-24Ch]  
  __int128 v71; // [esp+10470h] [ebp-23Ch]  
  __int128 v72; // [esp+10480h] [ebp-22Ch]  
  __int128 v73; // [esp+10490h] [ebp-21Ch]  
  __int128 v74; // [esp+104A0h] [ebp-20Ch]  
  __int128 v75; // [esp+104B0h] [ebp-1FCh]  
  __int128 v76; // [esp+104C0h] [ebp-1ECh]  
  __int128 v77; // [esp+104D0h] [ebp-1DCh]  
  __int128 v78; // [esp+104E0h] [ebp-1CCh]  
  __int128 v79; // [esp+104F0h] [ebp-1BCh]  
  __int128 v80; // [esp+10500h] [ebp-1ACh]  
  __int128 v81; // [esp+10510h] [ebp-19Ch]  
  __int128 v82; // [esp+10520h] [ebp-18Ch]  
  __int128 v83; // [esp+10530h] [ebp-17Ch]  
  __int128 v84; // [esp+10540h] [ebp-16Ch]  
  __int128 v85; // [esp+10550h] [ebp-15Ch]  
  __int128 v86; // [esp+10560h] [ebp-14Ch]  
  __int128 v87; // [esp+10570h] [ebp-13Ch]  
  __int128 v88; // [esp+10580h] [ebp-12Ch]  
  __int128 v89; // [esp+10590h] [ebp-11Ch]  
  __int128 v90; // [esp+105A0h] [ebp-10Ch]  
  __int128 v91; // [esp+105B0h] [ebp-FCh]  
  __int128 v92; // [esp+105C0h] [ebp-ECh]  
  __int128 v93; // [esp+105D0h] [ebp-DCh]  
  __int128 v94; // [esp+105E0h] [ebp-CCh]  
  __int128 v95; // [esp+105F0h] [ebp-BCh]  
  __int128 v96; // [esp+10600h] [ebp-ACh]  
  __int128 v97; // [esp+10610h] [ebp-9Ch]  
  __int128 v98; // [esp+10620h] [ebp-8Ch]  
  __int128 v99; // [esp+10630h] [ebp-7Ch]  
  __int128 v100; // [esp+10640h] [ebp-6Ch]  
  __int128 v101; // [esp+10650h] [ebp-5Ch]  
  __int128 v102; // [esp+10660h] [ebp-4Ch]  
  __int128 v103; // [esp+10670h] [ebp-3Ch]  
  __int128 v104; // [esp+10680h] [ebp-2Ch]  
  int v105; // [esp+10690h] [ebp-1Ch]  
  int v106; // [esp+10694h] [ebp-18h]  
  int v107; // [esp+10698h] [ebp-14h]  
  __int16 v108; // [esp+1069Ch] [ebp-10h]  
  int (*v109)(void); // [esp+106A0h] [ebp-Ch]  
  __int16 v110; // [esp+106A4h] [ebp-8h]  
  char v111; // [esp+106A6h] [ebp-6h]  

v47 = (HANDLE )lpThreadParameter;
CoInitialize(0);
if ( SHGetKnownFolderPath(&unk_10011190, 0, 0, &lpWideCharStr) )
return 1;
MultiByteStr = 0;
WideCharToMultiByte(0, 0, lpWideCharStr, -1, &MultiByteStr, 260, 0, 0);
CoTaskMemFree((LPVOID)lpWideCharStr);
sub_10003628(&MultiByteStr, (int)&v52, 0, 0, 0);
_makepath(&LibFileName, 0, &v52, "4pcl9.dll", 0); //获取4pcl9.dll绝对路径
v2 = LoadLibraryA(&LibFileName); //动态加载4pcl9.dll
v3 = v2;
if ( !v2 )
{
MessageBoxA(0, "Lua initialization failed.", "LunaLoader", 0);
MessageBoxA((HWND)v3, &LibFileName, "LunaLoader", (UINT)v3);
return 0;
}
v4 = GetProcAddress(v2, "luaL_newstate"); //初始化lua加载器
v5 = v4;
v109 = (int (
)(void))v4;
dword_10019248 = (int (__cdecl *)(_DWORD, _DWORD))GetProcAddress(v3, "luaL_loadstring"); //获取函数地址
dword_10019244 = (int (__cdecl *)(_DWORD, _DWORD, _DWORD))GetProcAddress(v3, "lua_dump");
v6 = GetProcAddress(v3, "lua_settop");
dword_1001923C = (int (__cdecl *)(_DWORD, _DWORD))v6;
if ( v5 && dword_10019248 && dword_10019244 && v6 )
{
GetFileAttributesA("...\elsword.exe");
v7 = CreateNamedPipeA("\.\pipe\LunaLoaderCommandPipe", 3u, 4u, 1u, 0, 0, 0, 0); //创建命名管道与登录器进行交互,获取用户传递的脚本字符串内容并且执行
v8 = v7;
hFile = v7;
if ( v7 && v7 != (HANDLE)-1 )
{
if ( ConnectNamedPipe(v7, 0) )
{
v9 = (char *)malloc(0x400u);
memset(v9, 0, 0x400u);
if ( !ReadFile(v8, v9, 0x3FFu, &NumberOfBytesRead, 0) )
goto LABEL_71;
v10 = NumberOfBytesRead;
if ( NumberOfBytesRead <= 1 )
goto LABEL_71;
v11 = *v9;
v12 = NumberOfBytesRead - 1;
v13 = v9;
if ( NumberOfBytesRead != 1 )
{
do
{
v14 = (v13++)[1];
v15 = v11++ ^ v14;
*(v13 - 1) = v15;
–v12;
}
while ( v12 );
v10 = NumberOfBytesRead;
}
NumberOfBytesRead = v10 - 1;
v9[NumberOfBytesRead] = 0;
v16 = (CHAR *)malloc(0x400u);
lpszVolumeMountPoint = v16;
memset(v16, 0, 0x400u);
if ( !ReadFile(v8, v16, 0x3FFu, &v42, 0) )
goto LABEL_71;
v17 = NumberOfBytesRead;
v18 = 0;
v19 = v9;
if ( NumberOfBytesRead )
{
v20 = lpszVolumeMountPoint;
do
{
*v19 ^= v20[v18];
++v19;
v18 = v18 + 1 < v42 ? v18 + 1 : 0;
–v17;
}
while ( v17 );
v8 = hFile;
}
v21 = (CHAR *)malloc(0x10u);
lpszVolumeMountPoint = v21;
*(_OWORD *)v21 = 0i64;
if ( ReadFile(v8, v21, 0xFu, &v41, 0) )
{
v22 = (CHAR *)malloc(0x41u);
memset(v22, 0, 0x41u);
v23 = v22 + 1;
GetVolumeNameForVolumeMountPointA(lpszVolumeMountPoint, v23, 0x40u);
v24 = (char *)malloc(0x40u);
v25 = *v23;
for ( i = v24; v25; v23 )
{
if ( (v25 >= 97 && v25 <= 102 || v25 >= 65 && v25 <= 70 || v25 >= 48 && v25 <= 57) && *(v23 - 1) != 109 )
*v24
= v25;
v25 = v23[1];
}
*v24 = 0;
GetSystemInfo(&SystemInfo);
v27 = (LPCSTR)SystemInfo.lpMinimumApplicationAddress;
for ( lpszVolumeMountPoint = (LPCSTR)SystemInfo.lpMaximumApplicationAddress;
v27 < lpszVolumeMountPoint;
v27 += Buffer.RegionSize )
{
VirtualQueryEx(*v47, v27, &Buffer, 0x1Cu);
if ( (Buffer.Protect == 2 || Buffer.Protect == 4 || Buffer.Protect == 32 || Buffer.Protect == 64)
&& Buffer.State == 4096 )
{
v28 = strcmp(v9, i);
if ( v28 )
v28 = -(v28 < 0) | 1;
if ( !v28 )
sub_10001150((int)Buffer.BaseAddress, Buffer.RegionSize);
}
}
CloseHandle(*v47);
v29 = &dword_100187C4;
if ( off_100187C8 )
{
while ( *v29 )
{
v29 += 3;
if ( !v29[1] )
goto LABEL_49;
}
}
else
{
LABEL_49:
v30 = strcmp(v9, i);
if ( v30 )
v30 = -(v30 < 0) | 1;
if ( !v30 )
{
lpszVolumeMountPoint = *(LPCSTR *)(**(_DWORD **)(dword_100187D0 + 1) + 4);
v47 = (HANDLE )dword_100187C4;
dword_10019240 = v109();
if ( dword_10019240 )
{
v55 = xmmword_10016850;
v56 = xmmword_100166A0;
v57 = xmmword_10016910;
v58 = xmmword_10016770;
v59 = xmmword_100167D0;
v60 = xmmword_10016960;
v61 = xmmword_10016660;
v62 = xmmword_10016840;
v63 = xmmword_100166B0;
v64 = xmmword_100168F0;
v65 = xmmword_10016740;
v66 = xmmword_100167C0;
v67 = xmmword_10016930;
v68 = xmmword_10016680;
v69 = xmmword_10016820;
v70 = xmmword_100166D0;
v71 = xmmword_100168C0;
v72 = xmmword_10016730;
v73 = xmmword_100167F0;
v74 = xmmword_10016940;
v75 = xmmword_10016670;
v76 = xmmword_10016860;
v77 = xmmword_100166E0;
v78 = xmmword_10016900;
v79 = xmmword_10016760;
v80 = xmmword_10016800;
v81 = xmmword_10016950;
v82 = xmmword_10016650;
v83 = xmmword_10016870;
v84 = xmmword_100166C0;
v85 = xmmword_100168E0;
v86 = xmmword_10016780;
v87 = xmmword_100167E0;
v88 = xmmword_10016790;
v89 = xmmword_10016700;
v90 = xmmword_10016830;
v91 = xmmword_10016710;
v92 = xmmword_100167B0;
v93 = xmmword_10016810;
v94 = xmmword_10016920;
v95 = xmmword_10016890;
v31 = 0;
v105 = -2004716861;
v32 = 0;
v96 = xmmword_100166F0;
v106 = -1359952088;
v97 = xmmword_100168A0;
v107 = 1973920377;
v98 = xmmword_10016720;
v108 = -22329;
v99 = xmmword_10016880;
v109 = (int (
)(void))1050454148;
v100 = xmmword_10016750;
v110 = -5641;
v101 = xmmword_100168B0;
v111 = 37;
v102 = xmmword_100167A0;
v103 = xmmword_10016690;
v104 = xmmword_100168D0;
do
{
*((_BYTE *)&v55 + v32) ^= *((_BYTE *)&v109 + v31);
v33 = (unsigned int)(v31 + 1) < 7 ? v31 + 1 : 0;
*((_BYTE *)&v55 + v32 + 1) ^= *((_BYTE *)&v109 + v33);
v32 += 2;
v31 = (unsigned int)(v33 + 1) < 7 ? v33 + 1 : 0;
}
while ( v32 < 0x32E );
v49 = 814;
qmemcpy(&v50, &v55, 0x32Cu);
v51 = v108;
v34 = malloc(0x100000u);
v35 = hFile;
v36 = v34;
v43 = 0;
while ( 1 )
{
memset(v36, 0, 0x100000u);
if ( ReadFile(v35, v36, 0xFFFFFu, &v43, 0) && v43 )
{
v37 = 0;
if ( !dword_10019248(dword_10019240, v36) )
{
v37 = malloc(0x10004u);
v38 = dword_10019240;
*v37 = 0;
if ( dword_10019244(v38, sub_10001200, v37) )
{
j___free_base(v37);
v37 = 0;
}
}
dword_1001923C(dword_10019240, -2);
if ( v37 )
{
((void (__cdecl *)(LPCSTR, _DWORD *, _DWORD))v47)(lpszVolumeMountPoint, v37 + 1, *v37);
j___free_base(v37);
}
}
Sleep(0x32u);
}
}
MessageBoxA(0, "Lua init error.", "LunaLoader", 0);
return 0;
}
}
MessageBoxA(0, "LunaLoader startup failed.", "LunaLoader", 0);
}
else
{
LABEL_71:
MessageBoxA(0, "Pipe read error.", "LunaLoader", 0);
CloseHandle(v8);
}
}
else
{
MessageBoxA(0, "Unable to connect to communication pipe.", "LunaLoader", 0);
CloseHandle(v8);
}
}
else
{
MessageBoxA(0, "Unable to open communication pipe.", "LunaLoader", 0);
}
}
else
{
MessageBoxA(0, "Internal error.", "LunaLoader", 0);
}
return 0;
}

dinput8.dll

Dll劫持

查看导出函数

随便查看一个,DllRegisterServer:

HRESULT __stdcall DllRegisterServer()  
{  
  int (*v1)(void); // [esp+D0h] [ebp-8h]  

if ( !hModule )
sub_10002670();
v1 = (int (*)(void))GetProcAddress(hModule, "DllRegisterServer");
if ( !v1 )
ExitProcess(0);
return v1();
}

可以看到很明显的Hook了DllRegisterServer函数,但是并未修改行为。

查看到DirectInput8Create函数:

__int64 __stdcall DirectInput8Create(int a1, int a2, int a3, _DWORD *a4, int a5)  
{  
  int v5; // edx  
  __int64 v6; // ST10_8  
  int v8; // [esp+Ch] [ebp-100h]  
  void *v9; // [esp+14h] [ebp-F8h]  
  int v10; // [esp+20h] [ebp-ECh]  
  int v11; // [esp+ECh] [ebp-20h]  
  int v12; // [esp+F8h] [ebp-14h]  
  FARPROC v13; // [esp+104h] [ebp-8h]  
  int savedregs; // [esp+10Ch] [ebp+0h]  

if ( !hModule )
sub_10002670();
v13 = GetProcAddress(hModule, "DirectInput8Create");
if ( !v13 )
ExitProcess(0);
if ( dword_100B9CE0 )
{
((void (__stdcall **)(int))((_DWORD *)dword_100B9CE0 + 4))(dword_100B9CE0);
v12 = 0;
}
else
{
v12 = ((int (__stdcall )(int, int, int, int , int))v13)(a1, a2, a3, &v11, a5);
if ( !v12 )
{
v9 = operator new(8u);
if ( v9 )
v8 = sub_10002BA0(v11);
else
v8 = 0;
v10 = v8;
dword_100B9CE0 = v8;
(
(void (__stdcall **)(int))(
(_DWORD *)v8 + 4))(v8);
}
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, 0, 0, 0);//主要操作
}
*a4 = dword_100B9CE0;
sub_10004C30(&savedregs, &dword_1000291C, v12, v5);
return v6;
}

StartAddress:

DWORD __stdcall StartAddress()  
{  
  LoadLibraryA("amdd3drt.dll");  
  return 0;  
}  

加载了amdd3drt.dll。

整体逻辑

至此,整个调用逻辑就已经理清楚了。首先释放器(LunaLoader-Launcher.exe)释放并启动登录器,然后登录器获取游戏进程并且找到其目录,释放dinput8.dll(DLL劫持)到该目录,劫持后,加载amdd3drt.dll(DLL注入),并创建命名管道与登录器进行通信,接收字符串并调用4pcl9.dll(Lua C库)中的方法。

搞了半天,其实就是将自己的Lua代码注入到游戏中执行。
然后来看一看提供的外挂脚本是如何实现的。
外挂的主要基于Hook游戏的函数,但是如何获取游戏中的函数以及参数呢?
作者提供了如下脚本:

--{ "Name":"Multi Function Dumper", "Type":"Login/Loading Screen", "Version": "1.0.0.0" }  
function file_exists(file)  
  local f = io.open(file, "rb")  
  if f then f:close() end  
  return f ~= nil  
end  
function lines_from(file)  
  if not file_exists(file) then return {} end  
  lines = {}  
  for line in io.lines(file) do  
    lines[#lines + 1] = line  
  end  
  return lines  
end  
local lines = lines_from("_functions.txt")  

local outfile = "_fdResult_clean.txt"
local f = io.open(outfile, "wb")
local outfunc = function(text)
f:write(text)
f:flush()
end
local funclist = {
{"CX2ItemManager", "AddShopItemList_LUA"}
}
local fromfile = false
if fromfile then
for k,v in pairs(lines) do
one, two = lines[k]:match("([.]+).([.]+)")
table.insert(funclist, { one, two })
outfunc("Found: [" … two … "]rn")
end
end

local dump = true
if dump then
local pack0
pack0 = function(tbl, idx, a, …)
tbl[idx] = a
if a then pack0(tbl, idx + 1, …) end
return tbl
end
local function pack(…)
return pack0({}, 1, …)
end

local tbldump
tbldump = function(tbl, outfunc)
if type(tbl) == "string" then
outfunc(""")
outfunc(tbl)
outfunc(""")
elseif type(tbl) == "table" then
outfunc("{")
for k, v in pairs(tbl) do
outfunc("[")
tbldump(k, outfunc)
outfunc("] = ")
tbldump(v, outfunc)
outfunc(", ")
end
outfunc("}")
else
outfunc(tostring(tbl))
end
end

outfunc("Starting.rn")

for k, v in pairs(funclist) do
local func = _G
local enclosing = nil
local enclosingKey = nil
for meh, fname in ipairs(v) do
enclosing = func
enclosingKey = fname
func = func[fname]
end

enclosing[enclosingKey] = function(...)  
    outfunc(tostring(enclosingKey) .. &#34;(&#34;)  
    local makecomma = false  
    for k, v in ipairs(pack(...)) do  
        if makecomma then outfunc(&#34;, &#34;) end  
        makecomma = true  
        tbldump(v, outfunc)  
    end  
    outfunc(&#34;)rn&#34;)  
    return func(...)  
end  

outfunc(&#34;Installed for &#34; .. tostring(enclosingKey) .. &#34;rn&#34;)  

end
end

首先可以从全局变量表_G中获取内存中的所有类以及方法名,然后通过上述脚本对该函数进行Hook:

    enclosing[enclosingKey] = function(...)  
        outfunc(tostring(enclosingKey) .. "(")  
        local makecomma = false  
        for k, v in ipairs(pack(...)) do  
            if makecomma then outfunc(", ") end  
            makecomma = true  
            tbldump(v, outfunc)  
        end  
        outfunc(")rn")  
        return func(...)  
    end  

通过迭代变长参数for k, v in ipairs(pack(...))获取到参数类型以及参数名,最后再传参调用原函数即可。

整体思路比较简单,游戏的反作弊系统几乎没有起到任何作用,除了DLL劫持还有许多方法可以bypass,此游戏之前甚至将明文Lua脚本放置于用户端,当时导致了大批用户使用非法手段影响游戏公平性。而这个外挂利用的是同种方法,基于游戏引擎特性的外挂,稳定性相较于直接修改内存时好了不止一星半点。

当前热门游戏中有使用XIGNCODE3的有:  

新玛奇英雄传
SF Online
黑色沙漠
跑跑卡丁车
冒险岛ㄧ
艾尔之光
《反恐精英Online》(游戏橘子)于2018年3月16日移除使用XIGNCODE3

怪不得这些游戏都死的差不多了

    </div>