垃圾桶

EL PSY CONGROO.

Lz1y's avatar Lz1y

逆向分析Lua注入类型外挂

相关

该网游类型为横版过关类角色扮演游戏,之前在这上面花了不少时间。刚接触逆向分析,所以比较好奇外挂以及游戏漏洞的原理,故写此记录一下分析过程。

找寻样本

该游戏近年在国内市场效应不佳,所以游戏工作室以及外挂制作等业务逐渐退出国服范围。在谷歌搜索后,发现存在许多类型为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 (Software\LunaLoader)的值,随机生成文件名,然后向其写入新文件名,然后在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)"\n\nBy 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 .. "]\r\n")  
    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.\r\n")  

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(")\r\n")  
        return func(...)  
    end  

    outfunc("Installed for " .. tostring(enclosingKey) .. "\r\n")  
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(")\r\n")  
        return func(...)  
    end  

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

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

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

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

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