• 注册
  • SPTarkov 动态 SPTarkov 动态 关注:128 内容:41

    Technical: inner workings of EFT caching system | 技术:EFT 缓存系统的内部工作原理

  • 查看作者
  • 打赏作者
  • 当前位置: ODDBA社区 > SPTarkov 动态 > 正文
    • 12
    • SPTarkov 动态
    • SPT主创
      圆转纯熟
      VIP2
      SPTarkov项目组

      Hello everyone! | 大家好!

      Today I want to present a technical document for those interested. I can understand that this might be too technical to share with the majority of users, but I hope that it still might be of interest to some. By sharing more of these kind of documents, I hope to inspire other users to take a peek into the inner workings of tarkov and learn programming.

      今天我想为有兴趣的人提供一份技术文档。 我可以理解这可能过于技术性,无法与大多数用户分享,但我希望它仍然可能引起某些人的兴趣。 通过分享更多此类文档,我希望能激励其他用户深入了解 tarkov 的内部工作原理并学习编程。

      Legal notice | 法律声明

      Reversing and discussing deobfuscated source is covered under Koninkrijk Der Nederlanden Auteurswet Artikel 45m 1c (english: Kingdom of The Netherlands Copyright Act Article 45m 1c) as this discussion is relevant to development of interoperability of the Aki project as an add-on module for Escape From Tarkov (also for those who don't know, yes I'm from The Netherlands).

      Koninkrijk Der Nederlanden Auteurswet Artikel 45m 1c(英语:荷兰王国版权法第 45m 1c 条)涵盖了逆向和讨论去混淆源,因为该讨论与作为 Escape From 附加模块的 Aki 项目的互操作性开发相关 Tarkov(对于那些不知道的人,是的,我来自荷兰)。

      I have a very hard time finding a translated text regarding copyright laws in China, so please let me know what those have to say regarding discussion of reverse engineering!

      我很难找到有关中国版权法的翻译文本,所以请让我知道那些关于逆向工程讨论的内容!

      Background | 背景

      While I am unaware of when caching was introduced, it's purpose is clear; saving bandwith and improving loading speed by storing heavier responses on the client side. In the early days of emutarkov, I was messing around with cache to find a way to extract response data from the game, as I didn't have access to the (matured) tools I have now. These were raw zlib compressed data.

      虽然我不知道缓存是什么时候引入的,但它的目的很明确; 通过在客户端存储更重的响应来节省带宽并提高加载速度。 在 emutarkov 的早期,我在缓存中寻找一种从游戏中提取响应数据的方法,因为我无法访问我现在拥有的(成熟的)工具。 这些是原始 zlib 压缩数据。

      Unknown to me at that time it was previously used to inject malicious values into the game for cheating purposes. Since then BSG has fixed this kind of exploit by verifying integrity of the cache server-side in 0.12.0. It was removed in 0.12.1 – 0.12.4.

      当时我并不知道它以前被用来将恶意值注入游戏以作弊。 从那时起,BSG 通过在 0.12.0 中验证缓存服务器端的完整性来修复这种漏洞。 它在 0.12.1 – 0.12.4 中被移除。

      With the release of 0.12.11 it was reintroduced with an XOR filter applied on top of the response to increase security.

      随着 0.12.11 的发布,它被重新引入,并在响应之上应用了 XOR 过滤器以提高安全性。

      Deobfuscated source | 去混淆源

      This is from 0.12.12.0.16029. This source can be obtained by deobfuscating the assembly and exporting it as a visual studio project.

      这是来自 0.12.12.0.16029。 可以通过对程序集进行反混淆并将其导出为 Visual Studio 项目来获得该源。

      Class Description 描述
      Class174 General Utilities 一般实用程序
      Class175 Game requests 游戏请求
      Class178 Cache generator 缓存生成器
      Class180 HTTP request handler HTTP 请求处理程序
      Class220 Logger 记录器
      Pooled9LevelZLib Zlib compression level 9 inflator / deflator Zlib 压缩级别 9 充气器/放气器

      public class Class174

      {
          public static string GetMd5Hash(HashAlgorithm md5Hash, string input)
          {
              byte[] array = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
              StringBuilder stringBuilder = new StringBuilder();
              for (int i = 0; i < array.Length; i++)
              {
                  stringBuilder.Append(array[i].ToString(“x2”));
              }
              return stringBuilder.ToString();
          }
      }

      public class Class178
      {
          private MD5 md5_0 = MD5.Create();

          private string method_0(string url)
          {
              string md5Hash = Class174.GetMd5Hash(this.md5_0, url);
              return this.string_0 + md5Hash;
          }

          public void Save(string url, string data)
          {
              if (!Directory.Exists(this.string_0))
              {
                  Directory.CreateDirectory(this.string_0);
              }
              string text = this.method_0(url);
              if (File.Exists(text))
              {
                  File.Delete(text);
              }
              if (string.IsNullOrEmpty(data))
              {
                  Debug.LogError(“Attempting to save empty cash file url: “ + url);
              }
              else
              {
                  byte[] array = ArrayPool<byte>.Shared.Rent(data.Length + 16);
                  int num = Pooled9LevelZLib.CompressToBytesNonAlloc(data, array, null);
                  for (int i = 0; i < num; i++)
                  {
                      array[i] ^= 13;
                  }
                  using (FileStream fileStream = File.Open(text, FileMode.OpenOrCreate))
                  {
                      fileStream.Write(array, 0, num);
                  }
                  ArrayPool<byte>.Shared.Return(array, false);
              }
          }

          public string Load(string url)
          {
              string text = this.method_0(url);
              if (File.Exists(text))
              {
                  byte[] array = File.ReadAllBytes(text);
                  for (int i = 0; i < array.Length; i++)
                  {
                      array[i] ^= 13;
                  }
                  string text2 = Pooled9LevelZLib.DecompressNonAlloc(array, array.Length, null);
                  return text2;
              }
              return null;
          }
      }

      File content | 文件内容

      In order to read the contents, we need to do the reverse of what BSG does for storing the file. In more technical terms: we need to iterate over each byte and negate the XOR, then inflate the level-9 zlib-compressed result. The CacheUtil also has the ability to modify existing cache by saving the content in the same format by doing the exact same as EFT.

      为了读取内容,我们需要执行与 BSG 存储文件相反的操作。 用更专业的术语来说:我们需要迭代每个字节并否定 XOR,然后膨胀第 9 级 zlib 压缩的结果。 CacheUtil 还能够通过执行与 EFT 完全相同的操作以相同格式保存内容来修改现有缓存。

      using System.Text;
      using Aki.Common.Utils;

      public static class CacheUtil
      {
          // 0.12.12.0.16029 Class178.Save xor comparison value
          private const int _xor = 13;

          public static string Load(string filepath)
          {
              byte[] bytes = File.ReadAllBytes(filepath);

              // negate xor
              for (int i = 0; i < bytes.Length; i++)
              {
                  bytes[i] ^= _xor;
              }

              // get decompressed utf8 text
              string data = Zlib.Decompress(bytes);
              return Encoding.UTF8.GetString(data);
          }

          public static void Save(string filepath, string json)
          {
              // get compressed utf8 text
              byte[] data = Encoding.UTF8.GetBytes(json);
              byte[] bytes = Zlib.Compress(data, ZlibCompression.Maximum);

              // apply xor
              for (int i = 0; i < bytes.Length; i++)
              {
                  bytes[i] ^= _xor;
              }

              File.WriteAllBytes(filepath, bytes);
          }
      }

      File name | 文件名

      The filenames are md5 hashes from the full request url. While we can't reverse these back to their original format, we can guess the correct one by regenerating it from the urls that we know are cached. Keep in mind that the host url (backendurl) might be different between sessions, and thus the default host url ( 链接) might not work.

      文件名是来自完整请求 url 的 md5 哈希值。 虽然我们无法将这些转换回它们的原始格式,但我们可以通过从我们知道已缓存的 url 重新生成它来猜测正确的格式。 请记住,会话之间的主机 url (backendurl) 可能不同,因此默认主机 url ( 链接) 可能不起作用。

      To know which requests are cached, search in Class175 for:

      要知道缓存了哪些请求,请在 Class175 中搜索:

      gstruct.UseCache = true;

      Here is how to generate the hash:

      以下是生成哈希的方法:

      using System.Security.Cryptography;
      using System.Text;

      public static class HashUtil
      {
          private static HashAlgorithm _md5;

          static HashUtil()
          {
              _md5 = MD5.Create();
          }

          public static string GetMd5Hash(string text)
          {
              byte[] bytes = _md5.ComputeHash(Encoding.UTF8.GetBytes(text));
              StringBuilder sr = new StringBuilder();

              foreach (byte item in bytes)
              {
                  sr.Append(item.ToString(“x2”));
              }

              return sr.ToString();
          }
      }

      In the event that you cannot generate the correct hash because of the changing backendurl, you can also look into the client logs (traces.log) to see which hash corresponds to the url which you try to obtain information from.

      如果由于更改 backendurl 而无法生成正确的哈希,您还可以查看客户端日志 (traces.log) 以查看哪个哈希对应于您尝试从中获取信息的 url。

      Putting it together | 把它放在一起

      Now that we know how to load and unload cache as well as getting the filename from an url, now it's time to put it in practice!

      现在我们知道如何加载和卸载缓存以及从 url 获取文件名,现在是时候付诸实践了!

      using System;

      public class Program
      {
          static void Main(string[] args)
          {
              // show hash of the chinese locale
              Console.WriteLine(HashUtil.GetHash(“http://127.0.0.1:6969/client/locale/ch”));

              // load a cache file and save it as json
              CacheUtil.Save(“extracted.json”, CacheUtil.Load(“cache/5b93ce20e1aa8e2619967f37154de6d7”));
          }
      }

      Conclusion | 结论

      Now you know what that weird cache folder is for, how the content is made and how to produce one yourself in code! Even if the content of this article is too hard to understand, I hope you learned something from it or enjoyed reading it.

      现在您知道那个奇怪的缓存文件夹是做什么的,内容是如何制作的,以及如何自己用代码生成一个! 即使这篇文章的内容太难理解,我也希望你能从中学到一些东西或者喜欢阅读。

      The main takeaway is that it takes a programmer to write code, it takes a better programmer to reverse the code, and a good programmer to understand and interface with it hahahaha  [s-15] 

      主要的收获是需要程序员来编写代码,需要更好的程序员来逆向代码,并且需要一个优秀的程序员来理解它并与之交互哈哈哈 [s-15]

      Don't hesitate to ask (non)technical questions in the comment section! No question is too foolish, a journey starts with a single step and progresses with each step, whenever that is failure, success or anything in between  [s-13] 

      不要犹豫,在评论部分提出(非)技术问题! 没有问题太愚蠢,旅程从一步开始,每一步都在进步,无论是失败、成功还是介于两者之间的任何事情 [s-13]

      SPTarkov 软件工程师

      初窥堂奥

      [s-26]

      回复
      登堂入室

      哇哦,好专业

      回复
      赠送了礼物[金币]
      回复
      渐入佳境

      厉害了我的大佬

      回复
      初窥堂奥

      表示没咋看懂,能说大概啥意思吗?新版本的毛病太多了,与其是不刷pmcAI

      回复
      登堂入室
      灯塔的任务在哪
      回复
      渐入佳境
      VIP3
      打赏了5金币
      回复
      登堂入室

      不愧是软件工程师!!!!

      回复
      登堂入室

      [s-7] 虽然看不懂,但是好强

      回复
      初来乍到

      所以现在外挂不能通过注入恶意值来实现功能了是吗

      So now the plug-in can't achieve the function by injecting malicious values, right?

      回复

      请登录之后再进行评论

      登录
    • 签到
    • 任务
    • 发布
    • 偏好设置
    • 帖子间隔 侧栏位置: