逆向分析Lua注入类型外挂
<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, @""), right), ".exe");
Functions.RegistryWrite("LastExecutable_Path", Conversions.ToString(obj2));
try
{
File.WriteAllBytes(Conversions.ToString(obj2), lunaLoader);
}
catch (Exception exception1)
{
ProjectData.SetProjectError(exception1);
Functions.Message("Can't write new program files.", Conversions.ToString(3));
ProjectData.ClearProjectError();
}
try
{
Process.Start(right, """ + directoryName + """);
}
catch (Exception exception2)
{
ProjectData.SetProjectError(exception2);
Functions.Message("The application files are corrupted, please disable your anti-virus.", Conversions.ToString(3));
ProjectData.ClearProjectError();
}
}
private void DeleteOld()
{
object obj2 = Functions.RegistryRead("LastExecutable_Path");
try
{
if (File.Exists(Conversions.ToString(obj2)))
{
File.Delete(Conversions.ToString(obj2));
}
}
catch (Exception exception1)
{
ProjectData.SetProjectError(exception1);
Functions.Message("Can't delete old program files.", 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) .. "(")
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
outfunc("Installed for " .. tostring(enclosingKey) .. "rn")
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>